Skip to main content

satay_runtime/
lib.rs

1#![forbid(unsafe_code)]
2
3use std::fmt;
4use std::str::FromStr;
5
6use http::header::{self, CONTENT_TYPE, HeaderName, HeaderValue};
7#[cfg(feature = "json")]
8use serde::de;
9use time::Month;
10use time::format_description::well_known::Rfc3339;
11pub use time::{Date, OffsetDateTime, PrimitiveDateTime, Time};
12
13use tracing::{debug, instrument};
14
15#[derive(Debug, Clone, PartialEq, Eq)]
16pub struct RequestParts<B> {
17    pub method: http::Method,
18    pub uri: String,
19    pub headers: http::HeaderMap,
20    pub body: B,
21}
22
23#[derive(Debug, Clone, PartialEq, Eq)]
24pub struct ResponseParts<B> {
25    pub status: http::StatusCode,
26    pub headers: http::HeaderMap,
27    pub body: B,
28}
29
30#[derive(Debug, thiserror::Error)]
31pub enum Error {
32    #[error("failed to build HTTP message: {0}")]
33    Http(#[from] http::Error),
34
35    #[error("invalid HTTP header value: {0}")]
36    InvalidHeaderValue(#[from] header::InvalidHeaderValue),
37
38    #[error("invalid HTTP header name: {0}")]
39    InvalidHeaderName(#[from] header::InvalidHeaderName),
40
41    #[error("missing required field `{0}`")]
42    MissingRequired(&'static str),
43
44    #[error("{0}")]
45    InvalidResponse(&'static str),
46
47    #[cfg(feature = "json")]
48    #[error("JSON error: {0}")]
49    Json(#[from] serde_json::Error),
50}
51
52#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
53pub enum ParseRangeError {
54    #[error("range contains more than one `-` separator")]
55    TooManySeparators,
56
57    #[error("invalid range minimum `{value}`: {message}")]
58    InvalidMinimum { value: String, message: String },
59
60    #[error("invalid range maximum `{value}`: {message}")]
61    InvalidMaximum { value: String, message: String },
62}
63
64#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
65pub enum ParseTimeError {
66    #[error("time must be exactly four ASCII digits in HHMM format")]
67    InvalidFormat,
68
69    #[error("time is outside valid HHMM range")]
70    ComponentRange,
71}
72
73#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
74pub enum ParseDateError {
75    #[error("date must be in YYYY-MM-DD format")]
76    InvalidFormat,
77}
78
79#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
80pub enum ParseNaiveDateTimeError {
81    #[error("datetime must be in YYYY-MM-DDTHH:mm:ss format")]
82    InvalidFormat,
83}
84
85pub trait Action {
86    type Response;
87
88    fn request(self) -> Result<http::Request<Vec<u8>>, Error>;
89    fn decode<B: AsRef<[u8]>>(response: ResponseParts<B>) -> Result<Self::Response, Error>;
90}
91
92#[instrument(skip_all, fields(method = %method, uri = %uri))]
93pub fn into_request<B>(
94    RequestParts {
95        method,
96        uri,
97        headers,
98        body,
99    }: RequestParts<B>,
100) -> Result<http::Request<B>, Error> {
101    debug!("building HTTP request");
102    let mut request = http::Request::builder()
103        .method(method)
104        .uri(uri)
105        .body(body)?;
106    *request.headers_mut() = headers;
107    Ok(request)
108}
109
110#[instrument(skip_all, fields(method = %method, uri = %uri))]
111pub fn into_empty_request(
112    RequestParts {
113        method,
114        uri,
115        headers,
116        body: _,
117    }: RequestParts<()>,
118) -> Result<http::Request<Vec<u8>>, Error> {
119    debug!("building empty HTTP request");
120    let mut request = http::Request::builder()
121        .method(method)
122        .uri(uri)
123        .body(vec![])?;
124    *request.headers_mut() = headers;
125    Ok(request)
126}
127
128#[cfg(feature = "json")]
129#[instrument(skip_all, fields(method = %method, uri = %uri))]
130pub fn into_json_request<T>(
131    RequestParts {
132        method,
133        uri,
134        headers,
135        body,
136    }: RequestParts<T>,
137) -> Result<http::Request<Vec<u8>>, Error>
138where
139    T: serde::Serialize,
140{
141    debug!("building JSON HTTP request");
142    let body = serde_json::to_vec(&body)?;
143    let mut request = http::Request::builder()
144        .method(method)
145        .uri(uri)
146        .body(body)?;
147    *request.headers_mut() = headers;
148    if !request.headers().contains_key(CONTENT_TYPE) {
149        request.headers_mut().insert(
150            CONTENT_TYPE,
151            http::HeaderValue::from_static("application/json"),
152        );
153    }
154    Ok(request)
155}
156
157#[cfg(feature = "json")]
158#[instrument(skip_all, fields(method = %method, uri = %uri))]
159pub fn into_optional_json_request<T>(
160    RequestParts {
161        method,
162        uri,
163        headers,
164        body,
165    }: RequestParts<Option<T>>,
166) -> Result<http::Request<Vec<u8>>, Error>
167where
168    T: serde::Serialize,
169{
170    match body {
171        Some(body) => into_json_request(RequestParts {
172            method,
173            uri,
174            headers,
175            body,
176        }),
177        None => into_empty_request(RequestParts {
178            method,
179            uri,
180            headers,
181            body: (),
182        }),
183    }
184}
185
186#[cfg(feature = "json")]
187#[instrument(skip_all)]
188pub fn from_json_slice<T>(body: &[u8]) -> Result<T, Error>
189where
190    T: de::DeserializeOwned,
191{
192    debug!("deserializing JSON response");
193    Ok(serde_json::from_slice(body)?)
194}
195
196pub fn append_path_segment(out: &mut String, value: &str) {
197    append_percent_encoded(out, value.as_bytes());
198}
199
200pub fn append_query_pair(out: &mut String, first: &mut bool, key: &str, value: &str) {
201    if *first {
202        out.push('?');
203        *first = false;
204    } else {
205        out.push('&');
206    }
207    append_percent_encoded(out, key.as_bytes());
208    out.push('=');
209    append_percent_encoded(out, value.as_bytes());
210}
211
212pub fn format_offset_datetime(value: &OffsetDateTime) -> String {
213    value.format(&Rfc3339).unwrap_or_else(|_| value.to_string())
214}
215
216pub fn format_unix_time(value: &OffsetDateTime) -> String {
217    value.unix_timestamp().to_string()
218}
219
220pub fn format_date(value: &Date) -> String {
221    format!(
222        "{:04}-{:02}-{:02}",
223        value.year(),
224        u8::from(value.month()),
225        value.day()
226    )
227}
228
229pub fn parse_date(value: &str) -> Result<Date, ParseDateError> {
230    let value = value.trim().as_bytes();
231    if value.len() != 10 || value[4] != b'-' || value[7] != b'-' {
232        return Err(ParseDateError::InvalidFormat);
233    }
234
235    for (index, byte) in value.iter().enumerate() {
236        if matches!(index, 4 | 7) {
237            if *byte != b'-' {
238                return Err(ParseDateError::InvalidFormat);
239            }
240        } else if !byte.is_ascii_digit() {
241            return Err(ParseDateError::InvalidFormat);
242        }
243    }
244
245    let year = parse_date_year(&value[0..4])?;
246    let month = parse_date_u8(&value[5..7])?;
247    let day = parse_date_u8(&value[8..10])?;
248    let month = Month::try_from(month).map_err(|_| ParseDateError::InvalidFormat)?;
249    Date::from_calendar_date(year, month, day).map_err(|_| ParseDateError::InvalidFormat)
250}
251
252fn parse_date_year(bytes: &[u8]) -> Result<i32, ParseDateError> {
253    let mut value = 0i32;
254    for byte in bytes {
255        value = value
256            .checked_mul(10)
257            .and_then(|value| value.checked_add(i32::from(*byte - b'0')))
258            .ok_or(ParseDateError::InvalidFormat)?;
259    }
260    Ok(value)
261}
262
263fn parse_date_u8(bytes: &[u8]) -> Result<u8, ParseDateError> {
264    let mut value = 0u16;
265    for byte in bytes {
266        value = value
267            .checked_mul(10)
268            .and_then(|value| value.checked_add(u16::from(*byte - b'0')))
269            .ok_or(ParseDateError::InvalidFormat)?;
270    }
271    u8::try_from(value).map_err(|_| ParseDateError::InvalidFormat)
272}
273
274pub fn format_naive_datetime(value: &PrimitiveDateTime) -> String {
275    format!(
276        "{}T{:02}:{:02}:{:02}",
277        format_date(&value.date()),
278        value.hour(),
279        value.minute(),
280        value.second()
281    )
282}
283
284pub fn parse_naive_datetime(value: &str) -> Result<PrimitiveDateTime, ParseNaiveDateTimeError> {
285    let value = value.trim();
286    let bytes = value.as_bytes();
287    if bytes.len() != 19
288        || bytes[4] != b'-'
289        || bytes[7] != b'-'
290        || bytes[10] != b'T'
291        || bytes[13] != b':'
292        || bytes[16] != b':'
293    {
294        return Err(ParseNaiveDateTimeError::InvalidFormat);
295    }
296
297    for (index, byte) in bytes.iter().enumerate() {
298        if matches!(index, 4 | 7 | 10 | 13 | 16) {
299            continue;
300        }
301        if !byte.is_ascii_digit() {
302            return Err(ParseNaiveDateTimeError::InvalidFormat);
303        }
304    }
305
306    let date = parse_date(&value[0..10]).map_err(|_| ParseNaiveDateTimeError::InvalidFormat)?;
307    let hour = parse_date_u8(&bytes[11..13]).map_err(|_| ParseNaiveDateTimeError::InvalidFormat)?;
308    let minute =
309        parse_date_u8(&bytes[14..16]).map_err(|_| ParseNaiveDateTimeError::InvalidFormat)?;
310    let second =
311        parse_date_u8(&bytes[17..19]).map_err(|_| ParseNaiveDateTimeError::InvalidFormat)?;
312    let time =
313        Time::from_hms(hour, minute, second).map_err(|_| ParseNaiveDateTimeError::InvalidFormat)?;
314    Ok(PrimitiveDateTime::new(date, time))
315}
316
317pub fn parse_time(value: &str) -> Result<Time, ParseTimeError> {
318    let value = value.trim();
319    let bytes = value.as_bytes();
320    if bytes.len() != 4 || !bytes.iter().all(u8::is_ascii_digit) {
321        return Err(ParseTimeError::InvalidFormat);
322    }
323
324    let hour = (bytes[0] - b'0') * 10 + (bytes[1] - b'0');
325    let minute = (bytes[2] - b'0') * 10 + (bytes[3] - b'0');
326    Time::from_hms(hour, minute, 0).map_err(|_| ParseTimeError::ComponentRange)
327}
328
329pub fn format_time(value: &Time) -> String {
330    format!("{:02}{:02}", value.hour(), value.minute())
331}
332
333pub fn format_bool(value: &bool) -> &'static str {
334    if *value { "1" } else { "0" }
335}
336
337pub fn parse_range<T>(value: &str) -> Result<(Option<T>, Option<T>), ParseRangeError>
338where
339    T: FromStr,
340    T::Err: fmt::Display,
341{
342    let value = value.trim();
343    if value.is_empty() {
344        return Ok((None, None));
345    }
346
347    let (min, max) = match value.split_once('-') {
348        Some((min, max)) => {
349            if max.contains('-') {
350                return Err(ParseRangeError::TooManySeparators);
351            }
352            (min, max)
353        }
354        None => (value, value),
355    };
356
357    Ok((parse_range_min(min)?, parse_range_max(max)?))
358}
359
360pub fn format_range<T>(min: &Option<T>, max: &Option<T>) -> String
361where
362    T: fmt::Display,
363{
364    match (min, max) {
365        (Some(min), Some(max)) => format!("{min}-{max}"),
366        (Some(min), None) => format!("{min}-"),
367        (None, Some(max)) => format!("-{max}"),
368        (None, None) => String::new(),
369    }
370}
371
372fn parse_range_min<T>(value: &str) -> Result<Option<T>, ParseRangeError>
373where
374    T: FromStr,
375    T::Err: fmt::Display,
376{
377    parse_range_bound(value, |value, message| ParseRangeError::InvalidMinimum {
378        value,
379        message,
380    })
381}
382
383fn parse_range_max<T>(value: &str) -> Result<Option<T>, ParseRangeError>
384where
385    T: FromStr,
386    T::Err: fmt::Display,
387{
388    parse_range_bound(value, |value, message| ParseRangeError::InvalidMaximum {
389        value,
390        message,
391    })
392}
393
394fn parse_range_bound<T>(
395    value: &str,
396    invalid: impl FnOnce(String, String) -> ParseRangeError,
397) -> Result<Option<T>, ParseRangeError>
398where
399    T: FromStr,
400    T::Err: fmt::Display,
401{
402    let value = value.trim();
403    if value.is_empty() {
404        return Ok(None);
405    }
406
407    value
408        .parse::<T>()
409        .map(Some)
410        .map_err(|err| invalid(value.to_owned(), err.to_string()))
411}
412
413#[cfg(feature = "serde")]
414pub mod serde_string {
415    use std::fmt;
416    use std::str::FromStr;
417
418    use serde::Deserialize;
419    use serde::de::Error as DeError;
420    use time::format_description::well_known::Rfc3339;
421
422    use crate::{Date, OffsetDateTime, PrimitiveDateTime, Time};
423
424    macro_rules! string_from_str_module {
425        ($module:ident, $ty:ty) => {
426            pub mod $module {
427                pub fn serialize<S>(value: &$ty, serializer: S) -> Result<S::Ok, S::Error>
428                where
429                    S: serde::Serializer,
430                {
431                    super::serialize_display(value, serializer)
432                }
433
434                pub fn deserialize<'de, D>(deserializer: D) -> Result<$ty, D::Error>
435                where
436                    D: serde::Deserializer<'de>,
437                {
438                    super::deserialize_from_str(deserializer)
439                }
440
441                pub mod option {
442                    pub fn serialize<S>(
443                        value: &Option<$ty>,
444                        serializer: S,
445                    ) -> Result<S::Ok, S::Error>
446                    where
447                        S: serde::Serializer,
448                    {
449                        super::super::serialize_option_display(value, serializer)
450                    }
451
452                    pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<$ty>, D::Error>
453                    where
454                        D: serde::Deserializer<'de>,
455                    {
456                        super::super::deserialize_option_from_str(deserializer)
457                    }
458                }
459            }
460        };
461    }
462
463    macro_rules! string_float_module {
464        ($module:ident, $ty:ty) => {
465            pub mod $module {
466                use serde::Deserialize;
467                use serde::de::Error as DeError;
468
469                pub fn serialize<S>(value: &$ty, serializer: S) -> Result<S::Ok, S::Error>
470                where
471                    S: serde::Serializer,
472                {
473                    super::serialize_display(value, serializer)
474                }
475
476                pub fn deserialize<'de, D>(deserializer: D) -> Result<$ty, D::Error>
477                where
478                    D: serde::Deserializer<'de>,
479                {
480                    let value = <String as Deserialize>::deserialize(deserializer)?;
481                    fast_float2::parse::<$ty, _>(&value).map_err(DeError::custom)
482                }
483
484                pub mod option {
485                    use serde::Deserialize;
486                    use serde::de::Error as DeError;
487
488                    pub fn serialize<S>(
489                        value: &Option<$ty>,
490                        serializer: S,
491                    ) -> Result<S::Ok, S::Error>
492                    where
493                        S: serde::Serializer,
494                    {
495                        super::super::serialize_option_display(value, serializer)
496                    }
497
498                    pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<$ty>, D::Error>
499                    where
500                        D: serde::Deserializer<'de>,
501                    {
502                        let value = <Option<String> as Deserialize>::deserialize(deserializer)?;
503                        value
504                            .map(|value| {
505                                fast_float2::parse::<$ty, _>(&value).map_err(DeError::custom)
506                            })
507                            .transpose()
508                    }
509                }
510            }
511        };
512    }
513
514    string_from_str_module!(as_u8, u8);
515    string_from_str_module!(as_u16, u16);
516    string_from_str_module!(as_u32, u32);
517    string_from_str_module!(as_u64, u64);
518    string_from_str_module!(as_i8, i8);
519    string_from_str_module!(as_i16, i16);
520    string_from_str_module!(as_i32, i32);
521    string_from_str_module!(as_i64, i64);
522    string_float_module!(as_f32, f32);
523    string_float_module!(as_f64, f64);
524
525    pub mod as_bool {
526        use std::fmt;
527
528        use serde::de::{Error as DeError, Visitor};
529
530        pub fn serialize<S>(value: &bool, serializer: S) -> Result<S::Ok, S::Error>
531        where
532            S: serde::Serializer,
533        {
534            serializer.serialize_str(crate::format_bool(value))
535        }
536
537        pub fn deserialize<'de, D>(deserializer: D) -> Result<bool, D::Error>
538        where
539            D: serde::Deserializer<'de>,
540        {
541            deserializer.deserialize_any(BoolVisitor)
542        }
543
544        struct BoolVisitor;
545
546        impl Visitor<'_> for BoolVisitor {
547            type Value = bool;
548
549            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
550                formatter.write_str("a boolean string or numeric boolean")
551            }
552
553            fn visit_bool<E>(self, value: bool) -> Result<Self::Value, E> {
554                Ok(value)
555            }
556
557            fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
558            where
559                E: DeError,
560            {
561                super::deserialize_bool(value).map_err(DeError::custom)
562            }
563
564            fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
565            where
566                E: DeError,
567            {
568                self.visit_str(&value)
569            }
570
571            fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E>
572            where
573                E: DeError,
574            {
575                match value {
576                    0 => Ok(false),
577                    1 => Ok(true),
578                    _ => Err(DeError::custom("invalid boolean number")),
579                }
580            }
581
582            fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
583            where
584                E: DeError,
585            {
586                match value {
587                    0 => Ok(false),
588                    1 => Ok(true),
589                    _ => Err(DeError::custom("invalid boolean number")),
590                }
591            }
592        }
593
594        pub mod option {
595            use std::fmt;
596
597            use serde::de::Visitor;
598
599            pub fn serialize<S>(value: &Option<bool>, serializer: S) -> Result<S::Ok, S::Error>
600            where
601                S: serde::Serializer,
602            {
603                match value {
604                    Some(value) => super::serialize(value, serializer),
605                    None => serializer.serialize_none(),
606                }
607            }
608
609            pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<bool>, D::Error>
610            where
611                D: serde::Deserializer<'de>,
612            {
613                deserializer.deserialize_option(BoolOptionVisitor)
614            }
615
616            struct BoolOptionVisitor;
617
618            impl<'de> Visitor<'de> for BoolOptionVisitor {
619                type Value = Option<bool>;
620
621                fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
622                    formatter.write_str("an optional boolean string or numeric boolean")
623                }
624
625                fn visit_none<E>(self) -> Result<Self::Value, E> {
626                    Ok(None)
627                }
628
629                fn visit_unit<E>(self) -> Result<Self::Value, E> {
630                    Ok(None)
631                }
632
633                fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
634                where
635                    D: serde::Deserializer<'de>,
636                {
637                    super::deserialize(deserializer).map(Some)
638                }
639            }
640        }
641    }
642
643    pub mod as_date {
644        use serde::Deserialize;
645        use serde::de::Error as DeError;
646
647        use super::*;
648
649        pub fn serialize<S>(value: &Date, serializer: S) -> Result<S::Ok, S::Error>
650        where
651            S: serde::Serializer,
652        {
653            serializer.serialize_str(&crate::format_date(value))
654        }
655
656        pub fn deserialize<'de, D>(deserializer: D) -> Result<Date, D::Error>
657        where
658            D: serde::Deserializer<'de>,
659        {
660            let value = <String as Deserialize>::deserialize(deserializer)?;
661            crate::parse_date(&value).map_err(DeError::custom)
662        }
663
664        pub mod option {
665            use serde::Deserialize;
666            use serde::de::Error as DeError;
667
668            use super::*;
669
670            pub fn serialize<S>(value: &Option<Date>, serializer: S) -> Result<S::Ok, S::Error>
671            where
672                S: serde::Serializer,
673            {
674                match value {
675                    Some(value) => super::serialize(value, serializer),
676                    None => serializer.serialize_none(),
677                }
678            }
679
680            pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Date>, D::Error>
681            where
682                D: serde::Deserializer<'de>,
683            {
684                let value = <Option<String> as Deserialize>::deserialize(deserializer)?;
685                value
686                    .map(|value| crate::parse_date(&value).map_err(DeError::custom))
687                    .transpose()
688            }
689        }
690    }
691
692    pub mod as_naive_datetime {
693        use serde::Deserialize;
694        use serde::de::Error as DeError;
695
696        use super::*;
697
698        pub fn serialize<S>(value: &PrimitiveDateTime, serializer: S) -> Result<S::Ok, S::Error>
699        where
700            S: serde::Serializer,
701        {
702            serializer.serialize_str(&crate::format_naive_datetime(value))
703        }
704
705        pub fn deserialize<'de, D>(deserializer: D) -> Result<PrimitiveDateTime, D::Error>
706        where
707            D: serde::Deserializer<'de>,
708        {
709            let value = <String as Deserialize>::deserialize(deserializer)?;
710            crate::parse_naive_datetime(&value).map_err(DeError::custom)
711        }
712
713        pub mod option {
714            use serde::Deserialize;
715            use serde::de::Error as DeError;
716
717            use super::*;
718
719            pub fn serialize<S>(
720                value: &Option<PrimitiveDateTime>,
721                serializer: S,
722            ) -> Result<S::Ok, S::Error>
723            where
724                S: serde::Serializer,
725            {
726                match value {
727                    Some(value) => super::serialize(value, serializer),
728                    None => serializer.serialize_none(),
729                }
730            }
731
732            pub fn deserialize<'de, D>(
733                deserializer: D,
734            ) -> Result<Option<PrimitiveDateTime>, D::Error>
735            where
736                D: serde::Deserializer<'de>,
737            {
738                let value = <Option<String> as Deserialize>::deserialize(deserializer)?;
739                value
740                    .map(|value| crate::parse_naive_datetime(&value).map_err(DeError::custom))
741                    .transpose()
742            }
743        }
744    }
745
746    pub mod as_offset_datetime {
747        use serde::Deserialize;
748        use serde::de::Error as DeError;
749        use serde::ser::Error as SerError;
750
751        use super::*;
752
753        pub fn serialize<S>(value: &OffsetDateTime, serializer: S) -> Result<S::Ok, S::Error>
754        where
755            S: serde::Serializer,
756        {
757            let value = value.format(&Rfc3339).map_err(SerError::custom)?;
758            serializer.serialize_str(&value)
759        }
760
761        pub fn deserialize<'de, D>(deserializer: D) -> Result<OffsetDateTime, D::Error>
762        where
763            D: serde::Deserializer<'de>,
764        {
765            let value = <String as Deserialize>::deserialize(deserializer)?;
766            OffsetDateTime::parse(&value, &Rfc3339).map_err(DeError::custom)
767        }
768
769        pub mod option {
770            use serde::Deserialize;
771            use serde::de::Error as DeError;
772
773            use super::*;
774
775            pub fn serialize<S>(
776                value: &Option<OffsetDateTime>,
777                serializer: S,
778            ) -> Result<S::Ok, S::Error>
779            where
780                S: serde::Serializer,
781            {
782                match value {
783                    Some(value) => super::serialize(value, serializer),
784                    None => serializer.serialize_none(),
785                }
786            }
787
788            pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<OffsetDateTime>, D::Error>
789            where
790                D: serde::Deserializer<'de>,
791            {
792                let value = <Option<String> as Deserialize>::deserialize(deserializer)?;
793                value
794                    .map(|value| OffsetDateTime::parse(&value, &Rfc3339).map_err(DeError::custom))
795                    .transpose()
796            }
797        }
798    }
799
800    pub mod as_unix_time {
801        use serde::Deserialize;
802        use serde::de::Error as DeError;
803
804        use super::*;
805
806        pub fn serialize<S>(value: &OffsetDateTime, serializer: S) -> Result<S::Ok, S::Error>
807        where
808            S: serde::Serializer,
809        {
810            serializer.serialize_str(&crate::format_unix_time(value))
811        }
812
813        pub fn deserialize<'de, D>(deserializer: D) -> Result<OffsetDateTime, D::Error>
814        where
815            D: serde::Deserializer<'de>,
816        {
817            let value = <String as Deserialize>::deserialize(deserializer)?;
818            let value = value.parse::<i64>().map_err(DeError::custom)?;
819            OffsetDateTime::from_unix_timestamp(value).map_err(DeError::custom)
820        }
821
822        pub mod option {
823            use serde::Deserialize;
824            use serde::de::Error as DeError;
825
826            use super::*;
827
828            pub fn serialize<S>(
829                value: &Option<OffsetDateTime>,
830                serializer: S,
831            ) -> Result<S::Ok, S::Error>
832            where
833                S: serde::Serializer,
834            {
835                match value {
836                    Some(value) => super::serialize(value, serializer),
837                    None => serializer.serialize_none(),
838                }
839            }
840
841            pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<OffsetDateTime>, D::Error>
842            where
843                D: serde::Deserializer<'de>,
844            {
845                let value = <Option<String> as Deserialize>::deserialize(deserializer)?;
846                value
847                    .map(|value| {
848                        let value = value.parse::<i64>().map_err(DeError::custom)?;
849                        OffsetDateTime::from_unix_timestamp(value).map_err(DeError::custom)
850                    })
851                    .transpose()
852            }
853        }
854    }
855
856    pub mod as_time {
857        use serde::Deserialize;
858        use serde::de::Error as DeError;
859
860        use super::*;
861
862        pub fn serialize<S>(value: &Time, serializer: S) -> Result<S::Ok, S::Error>
863        where
864            S: serde::Serializer,
865        {
866            serializer.serialize_str(&crate::format_time(value))
867        }
868
869        pub fn deserialize<'de, D>(deserializer: D) -> Result<Time, D::Error>
870        where
871            D: serde::Deserializer<'de>,
872        {
873            let value = <String as Deserialize>::deserialize(deserializer)?;
874            crate::parse_time(&value).map_err(DeError::custom)
875        }
876
877        pub mod option {
878            use serde::Deserialize;
879            use serde::de::Error as DeError;
880
881            use super::*;
882
883            pub fn serialize<S>(value: &Option<Time>, serializer: S) -> Result<S::Ok, S::Error>
884            where
885                S: serde::Serializer,
886            {
887                match value {
888                    Some(value) => super::serialize(value, serializer),
889                    None => serializer.serialize_none(),
890                }
891            }
892
893            pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Time>, D::Error>
894            where
895                D: serde::Deserializer<'de>,
896            {
897                let value = <Option<String> as Deserialize>::deserialize(deserializer)?;
898                let Some(value) = value else {
899                    return Ok(None);
900                };
901                let value = value.trim();
902                if value.is_empty() {
903                    return Ok(None);
904                }
905                crate::parse_time(value).map(Some).map_err(DeError::custom)
906            }
907        }
908    }
909
910    fn serialize_display<T, S>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
911    where
912        T: fmt::Display,
913        S: serde::Serializer,
914    {
915        serializer.serialize_str(&value.to_string())
916    }
917
918    fn serialize_option_display<T, S>(value: &Option<T>, serializer: S) -> Result<S::Ok, S::Error>
919    where
920        T: fmt::Display,
921        S: serde::Serializer,
922    {
923        match value {
924            Some(value) => serialize_display(value, serializer),
925            None => serializer.serialize_none(),
926        }
927    }
928
929    fn deserialize_from_str<'de, T, D>(deserializer: D) -> Result<T, D::Error>
930    where
931        T: FromStr,
932        T::Err: fmt::Display,
933        D: serde::Deserializer<'de>,
934    {
935        let value = String::deserialize(deserializer)?;
936        value.parse::<T>().map_err(DeError::custom)
937    }
938
939    fn deserialize_option_from_str<'de, T, D>(deserializer: D) -> Result<Option<T>, D::Error>
940    where
941        T: FromStr,
942        T::Err: fmt::Display,
943        D: serde::Deserializer<'de>,
944    {
945        let value = Option::<String>::deserialize(deserializer)?;
946        value
947            .map(|value| value.parse::<T>().map_err(DeError::custom))
948            .transpose()
949    }
950
951    fn deserialize_bool(value: &str) -> Result<bool, &'static str> {
952        match value {
953            "1" => Ok(true),
954            "0" => Ok(false),
955            value if value.eq_ignore_ascii_case("true") => Ok(true),
956            value if value.eq_ignore_ascii_case("false") => Ok(false),
957            _ => Err("invalid boolean string"),
958        }
959    }
960}
961
962#[cfg(feature = "serde")]
963pub mod serde_integer {
964    pub mod as_unix_time {
965        use serde::Deserialize;
966        use serde::de::Error as DeError;
967
968        use crate::OffsetDateTime;
969
970        pub fn serialize<S>(value: &OffsetDateTime, serializer: S) -> Result<S::Ok, S::Error>
971        where
972            S: serde::Serializer,
973        {
974            serializer.serialize_i64(value.unix_timestamp())
975        }
976
977        pub fn deserialize<'de, D>(deserializer: D) -> Result<OffsetDateTime, D::Error>
978        where
979            D: serde::Deserializer<'de>,
980        {
981            let value = <i64 as Deserialize>::deserialize(deserializer)?;
982            OffsetDateTime::from_unix_timestamp(value).map_err(DeError::custom)
983        }
984
985        pub mod option {
986            use serde::Deserialize;
987            use serde::de::Error as DeError;
988
989            use crate::OffsetDateTime;
990
991            pub fn serialize<S>(
992                value: &Option<OffsetDateTime>,
993                serializer: S,
994            ) -> Result<S::Ok, S::Error>
995            where
996                S: serde::Serializer,
997            {
998                match value {
999                    Some(value) => super::serialize(value, serializer),
1000                    None => serializer.serialize_none(),
1001                }
1002            }
1003
1004            pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<OffsetDateTime>, D::Error>
1005            where
1006                D: serde::Deserializer<'de>,
1007            {
1008                let value = <Option<i64> as Deserialize>::deserialize(deserializer)?;
1009                value
1010                    .map(|value| {
1011                        OffsetDateTime::from_unix_timestamp(value).map_err(DeError::custom)
1012                    })
1013                    .transpose()
1014            }
1015        }
1016    }
1017
1018    pub mod as_bool {
1019        use crate::serde_string::as_bool as string_bool;
1020
1021        pub fn serialize<S>(value: &bool, serializer: S) -> Result<S::Ok, S::Error>
1022        where
1023            S: serde::Serializer,
1024        {
1025            serializer.serialize_u8(u8::from(*value))
1026        }
1027
1028        pub fn deserialize<'de, D>(deserializer: D) -> Result<bool, D::Error>
1029        where
1030            D: serde::Deserializer<'de>,
1031        {
1032            string_bool::deserialize(deserializer)
1033        }
1034
1035        pub mod option {
1036            use crate::serde_string::as_bool::option as string_bool_option;
1037
1038            pub fn serialize<S>(value: &Option<bool>, serializer: S) -> Result<S::Ok, S::Error>
1039            where
1040                S: serde::Serializer,
1041            {
1042                match value {
1043                    Some(value) => super::serialize(value, serializer),
1044                    None => serializer.serialize_none(),
1045                }
1046            }
1047
1048            pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<bool>, D::Error>
1049            where
1050                D: serde::Deserializer<'de>,
1051            {
1052                string_bool_option::deserialize(deserializer)
1053            }
1054        }
1055    }
1056}
1057
1058#[cfg(feature = "json")]
1059pub mod treat_error_as_none {
1060    use serde::de::DeserializeOwned;
1061    use serde::{Deserialize, Deserializer, Serialize, Serializer};
1062
1063    pub fn serialize<S, T>(value: &Option<T>, serializer: S) -> Result<S::Ok, S::Error>
1064    where
1065        S: Serializer,
1066        T: Serialize,
1067    {
1068        match value {
1069            Some(inner) => inner.serialize(serializer),
1070            None => serializer.serialize_none(),
1071        }
1072    }
1073
1074    pub fn deserialize<'de, D, T>(deserializer: D) -> Result<Option<T>, D::Error>
1075    where
1076        D: Deserializer<'de>,
1077        T: DeserializeOwned,
1078    {
1079        let value = serde_json::Value::deserialize(deserializer)?;
1080        match T::deserialize(value) {
1081            Ok(parsed) => Ok(Some(parsed)),
1082            Err(_) => Ok(None),
1083        }
1084    }
1085}
1086
1087pub fn insert_header(
1088    headers: &mut http::HeaderMap,
1089    name: &'static str,
1090    value: &str,
1091) -> Result<(), Error> {
1092    headers.insert(
1093        HeaderName::from_bytes(name.as_bytes())?,
1094        HeaderValue::from_str(value)?,
1095    );
1096    Ok(())
1097}
1098
1099pub fn has_json_content_type(headers: &http::HeaderMap) -> bool {
1100    headers
1101        .get(CONTENT_TYPE)
1102        .and_then(|value| value.to_str().ok())
1103        .is_some_and(is_json_media_type)
1104}
1105
1106fn is_json_media_type(value: &str) -> bool {
1107    let media_type = value.split(';').next().unwrap_or(value).trim();
1108    if media_type.eq_ignore_ascii_case("application/json") {
1109        return true;
1110    }
1111
1112    let Some((_, subtype)) = media_type.rsplit_once('/') else {
1113        return false;
1114    };
1115    ends_with_ignore_ascii_case(subtype, "+json")
1116}
1117
1118fn ends_with_ignore_ascii_case(value: &str, suffix: &str) -> bool {
1119    let value = value.as_bytes();
1120    let suffix = suffix.as_bytes();
1121    value.len() >= suffix.len() && value[value.len() - suffix.len()..].eq_ignore_ascii_case(suffix)
1122}
1123
1124fn append_percent_encoded(out: &mut String, bytes: &[u8]) {
1125    const HEX: &[u8; 16] = b"0123456789ABCDEF";
1126
1127    for &byte in bytes {
1128        if is_unreserved(byte) {
1129            out.push(byte as char);
1130        } else {
1131            out.push('%');
1132            out.push(HEX[(byte >> 4) as usize] as char);
1133            out.push(HEX[(byte & 0x0f) as usize] as char);
1134        }
1135    }
1136}
1137
1138const fn is_unreserved(byte: u8) -> bool {
1139    matches!(
1140        byte,
1141        b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'.' | b'_' | b'~'
1142    )
1143}
1144
1145#[cfg(test)]
1146mod tests {
1147    use super::*;
1148
1149    #[test]
1150    fn encodes_path_segments() {
1151        let mut out = String::new();
1152        append_path_segment(&mut out, "a/b c");
1153        assert_eq!(out, "a%2Fb%20c");
1154    }
1155
1156    #[test]
1157    fn appends_query_pairs() {
1158        let mut out = String::from("/pets");
1159        let mut first = true;
1160        append_query_pair(&mut out, &mut first, "tag name", "small/dog");
1161        append_query_pair(&mut out, &mut first, "limit", "10");
1162        assert_eq!(out, "/pets?tag%20name=small%2Fdog&limit=10");
1163    }
1164
1165    #[test]
1166    fn parses_range_strings() {
1167        assert_eq!(parse_range::<u8>("14-17").unwrap(), (Some(14), Some(17)));
1168        assert_eq!(parse_range::<u8>("14-").unwrap(), (Some(14), None));
1169        assert_eq!(parse_range::<u8>("-17").unwrap(), (None, Some(17)));
1170        assert_eq!(parse_range::<u8>("").unwrap(), (None, None));
1171        assert!(matches!(
1172            parse_range::<u8>("14-17-20"),
1173            Err(ParseRangeError::TooManySeparators)
1174        ));
1175    }
1176
1177    #[test]
1178    fn formats_range_strings() {
1179        assert_eq!(format_range(&Some(14), &Some(17)), "14-17");
1180        assert_eq!(format_range(&Some(14), &None::<u8>), "14-");
1181        assert_eq!(format_range(&None::<u8>, &Some(17)), "-17");
1182        assert_eq!(format_range(&None::<u8>, &None::<u8>), "");
1183    }
1184
1185    #[test]
1186    fn parses_and_formats_time_strings() {
1187        let time = parse_time("0620").unwrap();
1188        assert_eq!(time.hour(), 6);
1189        assert_eq!(time.minute(), 20);
1190        assert_eq!(format_time(&time), "0620");
1191        assert_eq!(parse_time("6:20"), Err(ParseTimeError::InvalidFormat));
1192        assert_eq!(parse_time("2400"), Err(ParseTimeError::ComponentRange));
1193    }
1194
1195    #[test]
1196    fn parses_and_formats_date_strings() {
1197        let date = parse_date("2024-07-16").unwrap();
1198        assert_eq!(
1199            date,
1200            Date::from_calendar_date(2024, Month::July, 16).unwrap()
1201        );
1202        assert_eq!(format_date(&date), "2024-07-16");
1203        assert_eq!(parse_date("2024-7-16"), Err(ParseDateError::InvalidFormat));
1204        assert_eq!(parse_date("not-a-date"), Err(ParseDateError::InvalidFormat));
1205    }
1206
1207    #[test]
1208    fn parses_and_formats_naive_datetime_strings() {
1209        let datetime = parse_naive_datetime("2024-07-16T23:59:00").unwrap();
1210        assert_eq!(datetime.hour(), 23);
1211        assert_eq!(datetime.minute(), 59);
1212        assert_eq!(datetime.second(), 0);
1213        assert_eq!(format_naive_datetime(&datetime), "2024-07-16T23:59:00");
1214        assert_eq!(
1215            parse_naive_datetime("2024-07-16T23:59"),
1216            Err(ParseNaiveDateTimeError::InvalidFormat)
1217        );
1218        assert_eq!(
1219            parse_naive_datetime("2024-07-16 23:59:00"),
1220            Err(ParseNaiveDateTimeError::InvalidFormat)
1221        );
1222    }
1223
1224    #[test]
1225    fn formats_unix_time_seconds() {
1226        let datetime = OffsetDateTime::from_unix_timestamp(-1).unwrap();
1227        assert_eq!(format_unix_time(&datetime), "-1");
1228    }
1229
1230    #[cfg(all(feature = "serde", feature = "json"))]
1231    #[test]
1232    fn serde_string_naive_datetime_round_trips() {
1233        #[derive(serde::Deserialize, serde::Serialize)]
1234        struct Value {
1235            #[serde(with = "crate::serde_string::as_naive_datetime")]
1236            at: PrimitiveDateTime,
1237        }
1238
1239        let at = parse_naive_datetime("2024-07-16T23:59:00").unwrap();
1240        let encoded = serde_json::to_value(Value { at }).unwrap();
1241        assert_eq!(encoded, serde_json::json!({ "at": "2024-07-16T23:59:00" }));
1242
1243        let decoded = serde_json::from_value::<Value>(encoded).unwrap();
1244        assert_eq!(decoded.at, at);
1245    }
1246
1247    #[cfg(all(feature = "serde", feature = "json"))]
1248    #[test]
1249    fn serde_string_date_round_trips() {
1250        #[derive(serde::Deserialize, serde::Serialize)]
1251        struct Value {
1252            #[serde(with = "crate::serde_string::as_date")]
1253            day: Date,
1254        }
1255
1256        let date = Date::from_calendar_date(2024, Month::July, 16).unwrap();
1257        let encoded = serde_json::to_value(Value { day: date }).unwrap();
1258        assert_eq!(encoded, serde_json::json!({ "day": "2024-07-16" }));
1259
1260        let decoded = serde_json::from_value::<Value>(encoded).unwrap();
1261        assert_eq!(decoded.day, date);
1262    }
1263
1264    #[test]
1265    fn recognizes_json_content_types() {
1266        let mut headers = http::HeaderMap::new();
1267        headers.insert(
1268            CONTENT_TYPE,
1269            http::HeaderValue::from_static("application/problem+json; charset=utf-8"),
1270        );
1271        assert!(has_json_content_type(&headers));
1272    }
1273
1274    #[test]
1275    fn response_parts_holds_status_headers_body() {
1276        let mut headers = http::HeaderMap::new();
1277        headers.insert(
1278            CONTENT_TYPE,
1279            http::HeaderValue::from_static("application/json"),
1280        );
1281        let body = br#"{"ok":true}"#.to_vec();
1282        let parts = ResponseParts {
1283            status: http::StatusCode::OK,
1284            headers,
1285            body,
1286        };
1287        assert_eq!(parts.status, http::StatusCode::OK);
1288        assert_eq!(parts.headers.get(CONTENT_TYPE).unwrap(), "application/json");
1289        assert_eq!(parts.body, br#"{"ok":true}"#);
1290    }
1291
1292    #[cfg(all(feature = "serde", feature = "json"))]
1293    #[test]
1294    fn serde_string_bool_accepts_string_and_numeric_values() {
1295        #[derive(serde::Deserialize, serde::Serialize)]
1296        struct Value {
1297            #[serde(with = "crate::serde_string::as_bool")]
1298            monitored: bool,
1299        }
1300
1301        let numeric = serde_json::from_str::<Value>(r#"{"monitored":0}"#).unwrap();
1302        assert!(!numeric.monitored);
1303
1304        let string = serde_json::from_str::<Value>(r#"{"monitored":"1"}"#).unwrap();
1305        assert!(string.monitored);
1306
1307        let encoded = serde_json::to_value(Value { monitored: false }).unwrap();
1308        assert_eq!(encoded, serde_json::json!({ "monitored": "0" }));
1309    }
1310
1311    #[cfg(all(feature = "serde", feature = "json"))]
1312    #[test]
1313    fn serde_integer_bool_accepts_numeric_values() {
1314        #[derive(serde::Deserialize, serde::Serialize)]
1315        struct Value {
1316            #[serde(with = "crate::serde_integer::as_bool")]
1317            monitored: bool,
1318        }
1319
1320        let numeric = serde_json::from_str::<Value>(r#"{"monitored":0}"#).unwrap();
1321        assert!(!numeric.monitored);
1322
1323        let encoded = serde_json::to_value(Value { monitored: true }).unwrap();
1324        assert_eq!(encoded, serde_json::json!({ "monitored": 1 }));
1325    }
1326
1327    #[cfg(all(feature = "serde", feature = "json"))]
1328    #[test]
1329    fn serde_string_unix_time_round_trips() {
1330        #[derive(serde::Deserialize, serde::Serialize)]
1331        struct Value {
1332            #[serde(with = "crate::serde_string::as_unix_time")]
1333            at: OffsetDateTime,
1334        }
1335
1336        let at = OffsetDateTime::from_unix_timestamp(1_719_892_800).unwrap();
1337        let encoded = serde_json::to_value(Value { at }).unwrap();
1338        assert_eq!(encoded, serde_json::json!({ "at": "1719892800" }));
1339
1340        let decoded = serde_json::from_value::<Value>(encoded).unwrap();
1341        assert_eq!(decoded.at, at);
1342    }
1343
1344    #[cfg(all(feature = "serde", feature = "json"))]
1345    #[test]
1346    fn serde_integer_unix_time_round_trips_and_handles_null() {
1347        #[derive(serde::Deserialize, serde::Serialize)]
1348        struct Value {
1349            #[serde(with = "crate::serde_integer::as_unix_time")]
1350            at: OffsetDateTime,
1351            #[serde(with = "crate::serde_integer::as_unix_time::option")]
1352            maybe_at: Option<OffsetDateTime>,
1353        }
1354
1355        let at = OffsetDateTime::from_unix_timestamp(1_719_892_800).unwrap();
1356        let encoded = serde_json::to_value(Value { at, maybe_at: None }).unwrap();
1357        assert_eq!(
1358            encoded,
1359            serde_json::json!({ "at": 1719892800, "maybe_at": null })
1360        );
1361
1362        let decoded = serde_json::from_value::<Value>(encoded).unwrap();
1363        assert_eq!(decoded.at, at);
1364        assert_eq!(decoded.maybe_at, None);
1365    }
1366
1367    #[cfg(all(feature = "serde", feature = "json"))]
1368    #[test]
1369    fn serde_integer_unix_time_rejects_out_of_range_values() {
1370        #[derive(serde::Deserialize)]
1371        #[allow(dead_code)]
1372        struct Value {
1373            #[serde(with = "crate::serde_integer::as_unix_time")]
1374            at: OffsetDateTime,
1375        }
1376
1377        assert!(serde_json::from_str::<Value>(r#"{"at":9223372036854775807}"#).is_err());
1378    }
1379}