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;
9#[cfg(feature = "json")]
10pub use serde_json::Value as JsonValue;
11use time::Month;
12use time::format_description::well_known::Rfc3339;
13pub use time::{Date, OffsetDateTime, PrimitiveDateTime, Time};
14
15use tracing::{debug, instrument};
16
17#[derive(Debug, Clone, PartialEq, Eq)]
18pub struct RequestParts<B> {
19    pub method: http::Method,
20    pub uri: String,
21    pub headers: http::HeaderMap,
22    pub body: B,
23}
24
25#[derive(Debug, Clone, PartialEq, Eq)]
26pub struct ResponseParts<B> {
27    pub status: http::StatusCode,
28    pub headers: http::HeaderMap,
29    pub body: B,
30}
31
32#[derive(Debug, thiserror::Error)]
33pub enum Error {
34    #[error("failed to build HTTP message: {0}")]
35    Http(#[from] http::Error),
36
37    #[error("invalid HTTP header value: {0}")]
38    InvalidHeaderValue(#[from] header::InvalidHeaderValue),
39
40    #[error("invalid HTTP header name: {0}")]
41    InvalidHeaderName(#[from] header::InvalidHeaderName),
42
43    #[error("missing required field `{0}`")]
44    MissingRequired(&'static str),
45
46    #[error("{0}")]
47    InvalidResponse(&'static str),
48
49    #[cfg(feature = "json")]
50    #[error("JSON error: {0}")]
51    Json(#[from] serde_json::Error),
52}
53
54#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
55pub enum ParseRangeError {
56    #[error("range contains more than one `-` separator")]
57    TooManySeparators,
58
59    #[error("invalid range minimum `{value}`: {message}")]
60    InvalidMinimum { value: String, message: String },
61
62    #[error("invalid range maximum `{value}`: {message}")]
63    InvalidMaximum { value: String, message: String },
64}
65
66#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
67pub enum ParseTimeError {
68    #[error("time must be exactly four ASCII digits in HHMM format")]
69    InvalidFormat,
70
71    #[error("time is outside valid HHMM range")]
72    ComponentRange,
73}
74
75#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
76pub enum ParseDateError {
77    #[error("date must be in YYYY-MM-DD format")]
78    InvalidFormat,
79}
80
81#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
82pub enum ParseNaiveDateTimeError {
83    #[error("datetime must be in YYYY-MM-DDTHH:mm:ss format")]
84    InvalidFormat,
85}
86
87pub trait Action {
88    type Response;
89
90    /// Builds the HTTP request for this action.
91    ///
92    /// # Errors
93    ///
94    /// Returns an error if request construction or required input validation fails.
95    fn request(self) -> Result<http::Request<Vec<u8>>, Error>;
96
97    /// Decodes the HTTP response body into this action's response type.
98    ///
99    /// # Errors
100    ///
101    /// Returns an error if the response is invalid or cannot be decoded.
102    fn decode<B: AsRef<[u8]>>(response: ResponseParts<B>) -> Result<Self::Response, Error>;
103}
104
105/// Converts request parts into an HTTP request.
106///
107/// # Errors
108///
109/// Returns an error if the method, URI, or body cannot be converted into an HTTP request.
110#[instrument(skip_all, fields(method = %method, uri = %uri))]
111pub fn into_request<B>(
112    RequestParts {
113        method,
114        uri,
115        headers,
116        body,
117    }: RequestParts<B>,
118) -> Result<http::Request<B>, Error> {
119    debug!("building HTTP request");
120    let mut request = http::Request::builder()
121        .method(method)
122        .uri(uri)
123        .body(body)?;
124    *request.headers_mut() = headers;
125    Ok(request)
126}
127
128/// Converts request parts with an empty body into an HTTP request.
129///
130/// # Errors
131///
132/// Returns an error if the method or URI cannot be converted into an HTTP request.
133#[instrument(skip_all, fields(method = %method, uri = %uri))]
134pub fn into_empty_request(
135    RequestParts {
136        method,
137        uri,
138        headers,
139        body: (),
140    }: RequestParts<()>,
141) -> Result<http::Request<Vec<u8>>, Error> {
142    debug!("building empty HTTP request");
143    let mut request = http::Request::builder()
144        .method(method)
145        .uri(uri)
146        .body(vec![])?;
147    *request.headers_mut() = headers;
148    Ok(request)
149}
150
151/// Converts serializable request parts into a JSON HTTP request.
152///
153/// # Errors
154///
155/// Returns an error if JSON serialization fails or the HTTP request cannot be built.
156#[cfg(feature = "json")]
157#[instrument(skip_all, fields(method = %method, uri = %uri))]
158pub fn into_json_request<T>(
159    RequestParts {
160        method,
161        uri,
162        headers,
163        body,
164    }: RequestParts<T>,
165) -> Result<http::Request<Vec<u8>>, Error>
166where
167    T: serde::Serialize,
168{
169    debug!("building JSON HTTP request");
170    let body = serde_json::to_vec(&body)?;
171    let mut request = http::Request::builder()
172        .method(method)
173        .uri(uri)
174        .body(body)?;
175    *request.headers_mut() = headers;
176    if !request.headers().contains_key(CONTENT_TYPE) {
177        request.headers_mut().insert(
178            CONTENT_TYPE,
179            http::HeaderValue::from_static("application/json"),
180        );
181    }
182    Ok(request)
183}
184
185/// Converts optional serializable request parts into a JSON HTTP request.
186///
187/// # Errors
188///
189/// Returns an error if JSON serialization fails or the HTTP request cannot be built.
190#[cfg(feature = "json")]
191#[instrument(skip_all, fields(method = %method, uri = %uri))]
192pub fn into_optional_json_request<T>(
193    RequestParts {
194        method,
195        uri,
196        headers,
197        body,
198    }: RequestParts<Option<T>>,
199) -> Result<http::Request<Vec<u8>>, Error>
200where
201    T: serde::Serialize,
202{
203    match body {
204        Some(body) => into_json_request(RequestParts {
205            method,
206            uri,
207            headers,
208            body,
209        }),
210        None => into_empty_request(RequestParts {
211            method,
212            uri,
213            headers,
214            body: (),
215        }),
216    }
217}
218
219/// Deserializes a JSON response body from bytes.
220///
221/// # Errors
222///
223/// Returns an error if the body is not valid JSON for `T`.
224#[cfg(feature = "json")]
225#[instrument(skip_all)]
226pub fn from_json_slice<T>(body: &[u8]) -> Result<T, Error>
227where
228    T: de::DeserializeOwned,
229{
230    debug!("deserializing JSON response");
231    Ok(serde_json::from_slice(body)?)
232}
233
234pub fn append_path_segment(out: &mut String, value: &str) {
235    append_percent_encoded(out, value.as_bytes());
236}
237
238pub fn append_query_pair(out: &mut String, first: &mut bool, key: &str, value: &str) {
239    if *first {
240        out.push('?');
241        *first = false;
242    } else {
243        out.push('&');
244    }
245    append_percent_encoded(out, key.as_bytes());
246    out.push('=');
247    append_percent_encoded(out, value.as_bytes());
248}
249
250#[must_use]
251pub fn format_offset_datetime(value: &OffsetDateTime) -> String {
252    value.format(&Rfc3339).unwrap_or_else(|_| value.to_string())
253}
254
255#[must_use]
256pub fn format_unix_time(value: &OffsetDateTime) -> String {
257    value.unix_timestamp().to_string()
258}
259
260#[must_use]
261pub fn format_date(value: &Date) -> String {
262    format!(
263        "{:04}-{:02}-{:02}",
264        value.year(),
265        u8::from(value.month()),
266        value.day()
267    )
268}
269
270/// Parses a date in `YYYY-MM-DD` format.
271///
272/// # Errors
273///
274/// Returns an error if the value is not in `YYYY-MM-DD` format or is not a valid calendar date.
275pub fn parse_date(value: &str) -> Result<Date, ParseDateError> {
276    let value = value.trim().as_bytes();
277    if value.len() != 10 || value[4] != b'-' || value[7] != b'-' {
278        return Err(ParseDateError::InvalidFormat);
279    }
280
281    for (index, byte) in value.iter().enumerate() {
282        if matches!(index, 4 | 7) {
283            if *byte != b'-' {
284                return Err(ParseDateError::InvalidFormat);
285            }
286        } else if !byte.is_ascii_digit() {
287            return Err(ParseDateError::InvalidFormat);
288        }
289    }
290
291    let year = parse_date_year(&value[0..4])?;
292    let month = parse_date_u8(&value[5..7])?;
293    let day = parse_date_u8(&value[8..10])?;
294    let month = Month::try_from(month).map_err(|_| ParseDateError::InvalidFormat)?;
295    Date::from_calendar_date(year, month, day).map_err(|_| ParseDateError::InvalidFormat)
296}
297
298fn parse_date_year(bytes: &[u8]) -> Result<i32, ParseDateError> {
299    let mut value = 0i32;
300    for byte in bytes {
301        value = value
302            .checked_mul(10)
303            .and_then(|value| value.checked_add(i32::from(*byte - b'0')))
304            .ok_or(ParseDateError::InvalidFormat)?;
305    }
306    Ok(value)
307}
308
309fn parse_date_u8(bytes: &[u8]) -> Result<u8, ParseDateError> {
310    let mut value = 0u16;
311    for byte in bytes {
312        value = value
313            .checked_mul(10)
314            .and_then(|value| value.checked_add(u16::from(*byte - b'0')))
315            .ok_or(ParseDateError::InvalidFormat)?;
316    }
317    u8::try_from(value).map_err(|_| ParseDateError::InvalidFormat)
318}
319
320#[must_use]
321pub fn format_naive_datetime(value: &PrimitiveDateTime) -> String {
322    format!(
323        "{}T{:02}:{:02}:{:02}",
324        format_date(&value.date()),
325        value.hour(),
326        value.minute(),
327        value.second()
328    )
329}
330
331/// Parses a datetime in `YYYY-MM-DDTHH:mm:ss` format.
332///
333/// # Errors
334///
335/// Returns an error if the value is not in the expected format or has invalid date/time fields.
336pub fn parse_naive_datetime(value: &str) -> Result<PrimitiveDateTime, ParseNaiveDateTimeError> {
337    let value = value.trim();
338    let bytes = value.as_bytes();
339    if bytes.len() != 19
340        || bytes[4] != b'-'
341        || bytes[7] != b'-'
342        || bytes[10] != b'T'
343        || bytes[13] != b':'
344        || bytes[16] != b':'
345    {
346        return Err(ParseNaiveDateTimeError::InvalidFormat);
347    }
348
349    for (index, byte) in bytes.iter().enumerate() {
350        if matches!(index, 4 | 7 | 10 | 13 | 16) {
351            continue;
352        }
353        if !byte.is_ascii_digit() {
354            return Err(ParseNaiveDateTimeError::InvalidFormat);
355        }
356    }
357
358    let date = parse_date(&value[0..10]).map_err(|_| ParseNaiveDateTimeError::InvalidFormat)?;
359    let hour = parse_date_u8(&bytes[11..13]).map_err(|_| ParseNaiveDateTimeError::InvalidFormat)?;
360    let minute =
361        parse_date_u8(&bytes[14..16]).map_err(|_| ParseNaiveDateTimeError::InvalidFormat)?;
362    let second =
363        parse_date_u8(&bytes[17..19]).map_err(|_| ParseNaiveDateTimeError::InvalidFormat)?;
364    let time =
365        Time::from_hms(hour, minute, second).map_err(|_| ParseNaiveDateTimeError::InvalidFormat)?;
366    Ok(PrimitiveDateTime::new(date, time))
367}
368
369/// Parses a time in `HHMM` format.
370///
371/// # Errors
372///
373/// Returns an error if the value is not four ASCII digits or is outside the valid time range.
374pub fn parse_time(value: &str) -> Result<Time, ParseTimeError> {
375    let value = value.trim();
376    let bytes = value.as_bytes();
377    if bytes.len() != 4 || !bytes.iter().all(u8::is_ascii_digit) {
378        return Err(ParseTimeError::InvalidFormat);
379    }
380
381    let hour = (bytes[0] - b'0') * 10 + (bytes[1] - b'0');
382    let minute = (bytes[2] - b'0') * 10 + (bytes[3] - b'0');
383    Time::from_hms(hour, minute, 0).map_err(|_| ParseTimeError::ComponentRange)
384}
385
386#[must_use]
387pub fn format_time(value: &Time) -> String {
388    format!("{:02}{:02}", value.hour(), value.minute())
389}
390
391#[must_use]
392pub fn format_bool(value: &bool) -> &'static str {
393    if *value { "1" } else { "0" }
394}
395
396/// Parses an inclusive range string into optional minimum and maximum bounds.
397///
398/// # Errors
399///
400/// Returns an error if the range has too many separators or either bound cannot be parsed as `T`.
401pub fn parse_range<T>(value: &str) -> Result<(Option<T>, Option<T>), ParseRangeError>
402where
403    T: FromStr,
404    T::Err: fmt::Display,
405{
406    let value = value.trim();
407    if value.is_empty() {
408        return Ok((None, None));
409    }
410
411    let (min, max) = match value.split_once('-') {
412        Some((min, max)) => {
413            if max.contains('-') {
414                return Err(ParseRangeError::TooManySeparators);
415            }
416            (min, max)
417        }
418        None => (value, value),
419    };
420
421    Ok((parse_range_min(min)?, parse_range_max(max)?))
422}
423
424#[must_use]
425pub fn format_range<T>(min: &Option<T>, max: &Option<T>) -> String
426where
427    T: fmt::Display,
428{
429    match (min, max) {
430        (Some(min), Some(max)) => format!("{min}-{max}"),
431        (Some(min), None) => format!("{min}-"),
432        (None, Some(max)) => format!("-{max}"),
433        (None, None) => String::new(),
434    }
435}
436
437fn parse_range_min<T>(value: &str) -> Result<Option<T>, ParseRangeError>
438where
439    T: FromStr,
440    T::Err: fmt::Display,
441{
442    parse_range_bound(value, |value, message| ParseRangeError::InvalidMinimum {
443        value,
444        message,
445    })
446}
447
448fn parse_range_max<T>(value: &str) -> Result<Option<T>, ParseRangeError>
449where
450    T: FromStr,
451    T::Err: fmt::Display,
452{
453    parse_range_bound(value, |value, message| ParseRangeError::InvalidMaximum {
454        value,
455        message,
456    })
457}
458
459fn parse_range_bound<T>(
460    value: &str,
461    invalid: impl FnOnce(String, String) -> ParseRangeError,
462) -> Result<Option<T>, ParseRangeError>
463where
464    T: FromStr,
465    T::Err: fmt::Display,
466{
467    let value = value.trim();
468    if value.is_empty() {
469        return Ok(None);
470    }
471
472    value
473        .parse::<T>()
474        .map(Some)
475        .map_err(|err| invalid(value.to_owned(), err.to_string()))
476}
477
478#[cfg(feature = "serde")]
479#[allow(clippy::missing_errors_doc)]
480pub mod serde_string {
481    use std::fmt;
482    use std::str::FromStr;
483
484    use serde::Deserialize;
485    use serde::de::Error as DeError;
486    macro_rules! string_from_str_module {
487        ($module:ident, $ty:ty) => {
488            pub mod $module {
489                pub fn serialize<S>(value: &$ty, serializer: S) -> Result<S::Ok, S::Error>
490                where
491                    S: serde::Serializer,
492                {
493                    super::serialize_display(value, serializer)
494                }
495
496                pub fn deserialize<'de, D>(deserializer: D) -> Result<$ty, D::Error>
497                where
498                    D: serde::Deserializer<'de>,
499                {
500                    super::deserialize_from_str(deserializer)
501                }
502
503                pub fn serialize_none_if<S>(
504                    value: &Option<$ty>,
505                    none_value: &str,
506                    serializer: S,
507                ) -> Result<S::Ok, S::Error>
508                where
509                    S: serde::Serializer,
510                {
511                    super::serialize_option_or_sentinel(
512                        value.as_ref(),
513                        none_value,
514                        serializer,
515                        serialize,
516                    )
517                }
518
519                pub fn deserialize_none_if<'de, D>(
520                    deserializer: D,
521                    none_if: &[&str],
522                ) -> Result<Option<$ty>, D::Error>
523                where
524                    D: serde::Deserializer<'de>,
525                {
526                    super::deserialize_none_if_with(deserializer, none_if, str::parse::<$ty>)
527                }
528
529                pub mod option {
530                    pub fn serialize<S>(
531                        value: &Option<$ty>,
532                        serializer: S,
533                    ) -> Result<S::Ok, S::Error>
534                    where
535                        S: serde::Serializer,
536                    {
537                        super::super::serialize_option_display(value.as_ref(), serializer)
538                    }
539
540                    pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<$ty>, D::Error>
541                    where
542                        D: serde::Deserializer<'de>,
543                    {
544                        super::super::deserialize_option_from_str(deserializer)
545                    }
546
547                    pub fn deserialize_none_if<'de, D>(
548                        deserializer: D,
549                        none_if: &[&str],
550                    ) -> Result<Option<$ty>, D::Error>
551                    where
552                        D: serde::Deserializer<'de>,
553                    {
554                        super::super::deserialize_option_none_if_with(
555                            deserializer,
556                            none_if,
557                            str::parse::<$ty>,
558                        )
559                    }
560                }
561            }
562        };
563    }
564
565    macro_rules! string_float_module {
566        ($module:ident, $ty:ty) => {
567            pub mod $module {
568                use serde::Deserialize;
569                use serde::de::Error as DeError;
570
571                pub fn serialize<S>(value: &$ty, serializer: S) -> Result<S::Ok, S::Error>
572                where
573                    S: serde::Serializer,
574                {
575                    super::serialize_display(value, serializer)
576                }
577
578                pub fn deserialize<'de, D>(deserializer: D) -> Result<$ty, D::Error>
579                where
580                    D: serde::Deserializer<'de>,
581                {
582                    let value = <String as Deserialize>::deserialize(deserializer)?;
583                    fast_float2::parse::<$ty, _>(&value).map_err(DeError::custom)
584                }
585
586                pub fn serialize_none_if<S>(
587                    value: &Option<$ty>,
588                    none_value: &str,
589                    serializer: S,
590                ) -> Result<S::Ok, S::Error>
591                where
592                    S: serde::Serializer,
593                {
594                    super::serialize_option_or_sentinel(
595                        value.as_ref(),
596                        none_value,
597                        serializer,
598                        serialize,
599                    )
600                }
601
602                pub fn deserialize_none_if<'de, D>(
603                    deserializer: D,
604                    none_if: &[&str],
605                ) -> Result<Option<$ty>, D::Error>
606                where
607                    D: serde::Deserializer<'de>,
608                {
609                    super::deserialize_none_if_with(deserializer, none_if, |value| {
610                        fast_float2::parse::<$ty, _>(value)
611                    })
612                }
613
614                pub mod option {
615                    use serde::Deserialize;
616                    use serde::de::Error as DeError;
617
618                    pub fn serialize<S>(
619                        value: &Option<$ty>,
620                        serializer: S,
621                    ) -> Result<S::Ok, S::Error>
622                    where
623                        S: serde::Serializer,
624                    {
625                        super::super::serialize_option_display(value.as_ref(), serializer)
626                    }
627
628                    pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<$ty>, D::Error>
629                    where
630                        D: serde::Deserializer<'de>,
631                    {
632                        let value = <Option<String> as Deserialize>::deserialize(deserializer)?;
633                        value
634                            .map(|value| {
635                                fast_float2::parse::<$ty, _>(&value).map_err(DeError::custom)
636                            })
637                            .transpose()
638                    }
639
640                    pub fn deserialize_none_if<'de, D>(
641                        deserializer: D,
642                        none_if: &[&str],
643                    ) -> Result<Option<$ty>, D::Error>
644                    where
645                        D: serde::Deserializer<'de>,
646                    {
647                        super::super::deserialize_option_none_if_with(
648                            deserializer,
649                            none_if,
650                            |value| fast_float2::parse::<$ty, _>(value),
651                        )
652                    }
653                }
654            }
655        };
656    }
657
658    string_from_str_module!(as_u8, u8);
659    string_from_str_module!(as_u16, u16);
660    string_from_str_module!(as_u32, u32);
661    string_from_str_module!(as_u64, u64);
662    string_from_str_module!(as_i8, i8);
663    string_from_str_module!(as_i16, i16);
664    string_from_str_module!(as_i32, i32);
665    string_from_str_module!(as_i64, i64);
666    string_float_module!(as_f32, f32);
667    string_float_module!(as_f64, f64);
668
669    pub mod as_bool {
670        use std::fmt;
671
672        use serde::de::{Error as DeError, Visitor};
673
674        pub fn serialize<S>(value: &bool, serializer: S) -> Result<S::Ok, S::Error>
675        where
676            S: serde::Serializer,
677        {
678            serializer.serialize_str(crate::format_bool(value))
679        }
680
681        pub fn deserialize<'de, D>(deserializer: D) -> Result<bool, D::Error>
682        where
683            D: serde::Deserializer<'de>,
684        {
685            deserializer.deserialize_any(BoolVisitor)
686        }
687
688        pub fn serialize_none_if<S>(
689            value: &Option<bool>,
690            none_value: &str,
691            serializer: S,
692        ) -> Result<S::Ok, S::Error>
693        where
694            S: serde::Serializer,
695        {
696            super::serialize_option_or_sentinel(value.as_ref(), none_value, serializer, serialize)
697        }
698
699        pub fn deserialize_none_if<'de, D>(
700            deserializer: D,
701            none_if: &[&str],
702        ) -> Result<Option<bool>, D::Error>
703        where
704            D: serde::Deserializer<'de>,
705        {
706            deserializer.deserialize_any(BoolNoneIfVisitor { none_if })
707        }
708
709        struct BoolVisitor;
710
711        impl Visitor<'_> for BoolVisitor {
712            type Value = bool;
713
714            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
715                formatter.write_str("a boolean string or numeric boolean")
716            }
717
718            fn visit_bool<E>(self, value: bool) -> Result<Self::Value, E> {
719                Ok(value)
720            }
721
722            fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
723            where
724                E: DeError,
725            {
726                super::deserialize_bool(value).map_err(DeError::custom)
727            }
728
729            fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
730            where
731                E: DeError,
732            {
733                self.visit_str(&value)
734            }
735
736            fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E>
737            where
738                E: DeError,
739            {
740                match value {
741                    0 => Ok(false),
742                    1 => Ok(true),
743                    _ => Err(DeError::custom("invalid boolean number")),
744                }
745            }
746
747            fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
748            where
749                E: DeError,
750            {
751                match value {
752                    0 => Ok(false),
753                    1 => Ok(true),
754                    _ => Err(DeError::custom("invalid boolean number")),
755                }
756            }
757        }
758
759        struct BoolNoneIfVisitor<'a> {
760            none_if: &'a [&'a str],
761        }
762
763        impl Visitor<'_> for BoolNoneIfVisitor<'_> {
764            type Value = Option<bool>;
765
766            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
767                formatter.write_str("a boolean string, numeric boolean, or configured sentinel")
768            }
769
770            fn visit_bool<E>(self, value: bool) -> Result<Self::Value, E> {
771                Ok(Some(value))
772            }
773
774            fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
775            where
776                E: DeError,
777            {
778                if self.none_if.contains(&value) {
779                    Ok(None)
780                } else {
781                    super::deserialize_bool(value)
782                        .map(Some)
783                        .map_err(DeError::custom)
784                }
785            }
786
787            fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
788            where
789                E: DeError,
790            {
791                self.visit_str(&value)
792            }
793
794            fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E>
795            where
796                E: DeError,
797            {
798                match value {
799                    0 => Ok(Some(false)),
800                    1 => Ok(Some(true)),
801                    _ => Err(DeError::custom("invalid boolean number")),
802                }
803            }
804
805            fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
806            where
807                E: DeError,
808            {
809                match value {
810                    0 => Ok(Some(false)),
811                    1 => Ok(Some(true)),
812                    _ => Err(DeError::custom("invalid boolean number")),
813                }
814            }
815        }
816
817        pub mod option {
818            use std::fmt;
819
820            use serde::de::Visitor;
821
822            pub fn serialize<S>(value: &Option<bool>, serializer: S) -> Result<S::Ok, S::Error>
823            where
824                S: serde::Serializer,
825            {
826                match value {
827                    Some(value) => super::serialize(value, serializer),
828                    None => serializer.serialize_none(),
829                }
830            }
831
832            pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<bool>, D::Error>
833            where
834                D: serde::Deserializer<'de>,
835            {
836                deserializer.deserialize_option(BoolOptionVisitor)
837            }
838
839            pub fn deserialize_none_if<'de, D>(
840                deserializer: D,
841                none_if: &[&str],
842            ) -> Result<Option<bool>, D::Error>
843            where
844                D: serde::Deserializer<'de>,
845            {
846                deserializer.deserialize_option(BoolOptionNoneIfVisitor { none_if })
847            }
848
849            struct BoolOptionVisitor;
850
851            impl<'de> Visitor<'de> for BoolOptionVisitor {
852                type Value = Option<bool>;
853
854                fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
855                    formatter.write_str("an optional boolean string or numeric boolean")
856                }
857
858                fn visit_none<E>(self) -> Result<Self::Value, E> {
859                    Ok(None)
860                }
861
862                fn visit_unit<E>(self) -> Result<Self::Value, E> {
863                    Ok(None)
864                }
865
866                fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
867                where
868                    D: serde::Deserializer<'de>,
869                {
870                    super::deserialize(deserializer).map(Some)
871                }
872            }
873
874            struct BoolOptionNoneIfVisitor<'a> {
875                none_if: &'a [&'a str],
876            }
877
878            impl<'de> Visitor<'de> for BoolOptionNoneIfVisitor<'_> {
879                type Value = Option<bool>;
880
881                fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
882                    formatter.write_str("an optional boolean string, numeric boolean, or sentinel")
883                }
884
885                fn visit_none<E>(self) -> Result<Self::Value, E> {
886                    Ok(None)
887                }
888
889                fn visit_unit<E>(self) -> Result<Self::Value, E> {
890                    Ok(None)
891                }
892
893                fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
894                where
895                    D: serde::Deserializer<'de>,
896                {
897                    deserializer.deserialize_any(super::BoolNoneIfVisitor {
898                        none_if: self.none_if,
899                    })
900                }
901            }
902        }
903    }
904
905    pub mod as_date {
906        use serde::Deserialize;
907        use serde::de::Error as DeError;
908
909        use crate::Date;
910
911        pub fn serialize<S>(value: &Date, serializer: S) -> Result<S::Ok, S::Error>
912        where
913            S: serde::Serializer,
914        {
915            serializer.serialize_str(&crate::format_date(value))
916        }
917
918        pub fn deserialize<'de, D>(deserializer: D) -> Result<Date, D::Error>
919        where
920            D: serde::Deserializer<'de>,
921        {
922            let value = <String as Deserialize>::deserialize(deserializer)?;
923            crate::parse_date(&value).map_err(DeError::custom)
924        }
925
926        pub fn serialize_none_if<S>(
927            value: &Option<Date>,
928            none_value: &str,
929            serializer: S,
930        ) -> Result<S::Ok, S::Error>
931        where
932            S: serde::Serializer,
933        {
934            super::serialize_option_or_sentinel(value.as_ref(), none_value, serializer, serialize)
935        }
936
937        pub fn deserialize_none_if<'de, D>(
938            deserializer: D,
939            none_if: &[&str],
940        ) -> Result<Option<Date>, D::Error>
941        where
942            D: serde::Deserializer<'de>,
943        {
944            super::deserialize_none_if_with(deserializer, none_if, crate::parse_date)
945        }
946
947        pub mod option {
948            use serde::Deserialize;
949            use serde::de::Error as DeError;
950
951            use super::super as serde_string;
952            use crate::Date;
953
954            pub fn serialize<S>(value: &Option<Date>, serializer: S) -> Result<S::Ok, S::Error>
955            where
956                S: serde::Serializer,
957            {
958                match value {
959                    Some(value) => super::serialize(value, serializer),
960                    None => serializer.serialize_none(),
961                }
962            }
963
964            pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Date>, D::Error>
965            where
966                D: serde::Deserializer<'de>,
967            {
968                let value = <Option<String> as Deserialize>::deserialize(deserializer)?;
969                value
970                    .map(|value| crate::parse_date(&value).map_err(DeError::custom))
971                    .transpose()
972            }
973
974            pub fn deserialize_none_if<'de, D>(
975                deserializer: D,
976                none_if: &[&str],
977            ) -> Result<Option<Date>, D::Error>
978            where
979                D: serde::Deserializer<'de>,
980            {
981                serde_string::deserialize_option_none_if_with(
982                    deserializer,
983                    none_if,
984                    crate::parse_date,
985                )
986            }
987        }
988    }
989
990    pub mod as_naive_datetime {
991        use serde::Deserialize;
992        use serde::de::Error as DeError;
993
994        use crate::PrimitiveDateTime;
995
996        pub fn serialize<S>(value: &PrimitiveDateTime, serializer: S) -> Result<S::Ok, S::Error>
997        where
998            S: serde::Serializer,
999        {
1000            serializer.serialize_str(&crate::format_naive_datetime(value))
1001        }
1002
1003        pub fn deserialize<'de, D>(deserializer: D) -> Result<PrimitiveDateTime, D::Error>
1004        where
1005            D: serde::Deserializer<'de>,
1006        {
1007            let value = <String as Deserialize>::deserialize(deserializer)?;
1008            crate::parse_naive_datetime(&value).map_err(DeError::custom)
1009        }
1010
1011        pub fn serialize_none_if<S>(
1012            value: &Option<PrimitiveDateTime>,
1013            none_value: &str,
1014            serializer: S,
1015        ) -> Result<S::Ok, S::Error>
1016        where
1017            S: serde::Serializer,
1018        {
1019            super::serialize_option_or_sentinel(value.as_ref(), none_value, serializer, serialize)
1020        }
1021
1022        pub fn deserialize_none_if<'de, D>(
1023            deserializer: D,
1024            none_if: &[&str],
1025        ) -> Result<Option<PrimitiveDateTime>, D::Error>
1026        where
1027            D: serde::Deserializer<'de>,
1028        {
1029            super::deserialize_none_if_with(deserializer, none_if, crate::parse_naive_datetime)
1030        }
1031
1032        pub mod option {
1033            use serde::Deserialize;
1034            use serde::de::Error as DeError;
1035
1036            use super::super as serde_string;
1037            use crate::PrimitiveDateTime;
1038
1039            pub fn serialize<S>(
1040                value: &Option<PrimitiveDateTime>,
1041                serializer: S,
1042            ) -> Result<S::Ok, S::Error>
1043            where
1044                S: serde::Serializer,
1045            {
1046                match value {
1047                    Some(value) => super::serialize(value, serializer),
1048                    None => serializer.serialize_none(),
1049                }
1050            }
1051
1052            pub fn deserialize<'de, D>(
1053                deserializer: D,
1054            ) -> Result<Option<PrimitiveDateTime>, D::Error>
1055            where
1056                D: serde::Deserializer<'de>,
1057            {
1058                let value = <Option<String> as Deserialize>::deserialize(deserializer)?;
1059                value
1060                    .map(|value| crate::parse_naive_datetime(&value).map_err(DeError::custom))
1061                    .transpose()
1062            }
1063
1064            pub fn deserialize_none_if<'de, D>(
1065                deserializer: D,
1066                none_if: &[&str],
1067            ) -> Result<Option<PrimitiveDateTime>, D::Error>
1068            where
1069                D: serde::Deserializer<'de>,
1070            {
1071                serde_string::deserialize_option_none_if_with(
1072                    deserializer,
1073                    none_if,
1074                    crate::parse_naive_datetime,
1075                )
1076            }
1077        }
1078    }
1079
1080    pub mod as_offset_datetime {
1081        use serde::Deserialize;
1082        use serde::de::Error as DeError;
1083        use serde::ser::Error as SerError;
1084
1085        use crate::OffsetDateTime;
1086        use time::format_description::well_known::Rfc3339;
1087
1088        pub fn serialize<S>(value: &OffsetDateTime, serializer: S) -> Result<S::Ok, S::Error>
1089        where
1090            S: serde::Serializer,
1091        {
1092            let value = value.format(&Rfc3339).map_err(SerError::custom)?;
1093            serializer.serialize_str(&value)
1094        }
1095
1096        pub fn deserialize<'de, D>(deserializer: D) -> Result<OffsetDateTime, D::Error>
1097        where
1098            D: serde::Deserializer<'de>,
1099        {
1100            let value = <String as Deserialize>::deserialize(deserializer)?;
1101            OffsetDateTime::parse(&value, &Rfc3339).map_err(DeError::custom)
1102        }
1103
1104        pub fn serialize_none_if<S>(
1105            value: &Option<OffsetDateTime>,
1106            none_value: &str,
1107            serializer: S,
1108        ) -> Result<S::Ok, S::Error>
1109        where
1110            S: serde::Serializer,
1111        {
1112            super::serialize_option_or_sentinel(value.as_ref(), none_value, serializer, serialize)
1113        }
1114
1115        pub fn deserialize_none_if<'de, D>(
1116            deserializer: D,
1117            none_if: &[&str],
1118        ) -> Result<Option<OffsetDateTime>, D::Error>
1119        where
1120            D: serde::Deserializer<'de>,
1121        {
1122            super::deserialize_none_if_with(deserializer, none_if, |value| {
1123                OffsetDateTime::parse(value, &Rfc3339)
1124            })
1125        }
1126
1127        pub mod option {
1128            use serde::Deserialize;
1129            use serde::de::Error as DeError;
1130
1131            use super::super as serde_string;
1132            use crate::OffsetDateTime;
1133            use time::format_description::well_known::Rfc3339;
1134
1135            pub fn serialize<S>(
1136                value: &Option<OffsetDateTime>,
1137                serializer: S,
1138            ) -> Result<S::Ok, S::Error>
1139            where
1140                S: serde::Serializer,
1141            {
1142                match value {
1143                    Some(value) => super::serialize(value, serializer),
1144                    None => serializer.serialize_none(),
1145                }
1146            }
1147
1148            pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<OffsetDateTime>, D::Error>
1149            where
1150                D: serde::Deserializer<'de>,
1151            {
1152                let value = <Option<String> as Deserialize>::deserialize(deserializer)?;
1153                value
1154                    .map(|value| OffsetDateTime::parse(&value, &Rfc3339).map_err(DeError::custom))
1155                    .transpose()
1156            }
1157
1158            pub fn deserialize_none_if<'de, D>(
1159                deserializer: D,
1160                none_if: &[&str],
1161            ) -> Result<Option<OffsetDateTime>, D::Error>
1162            where
1163                D: serde::Deserializer<'de>,
1164            {
1165                serde_string::deserialize_option_none_if_with(deserializer, none_if, |value| {
1166                    OffsetDateTime::parse(value, &Rfc3339)
1167                })
1168            }
1169        }
1170    }
1171
1172    pub mod as_unix_time {
1173        use serde::Deserialize;
1174        use serde::de::Error as DeError;
1175
1176        use crate::OffsetDateTime;
1177
1178        pub fn serialize<S>(value: &OffsetDateTime, serializer: S) -> Result<S::Ok, S::Error>
1179        where
1180            S: serde::Serializer,
1181        {
1182            serializer.serialize_str(&crate::format_unix_time(value))
1183        }
1184
1185        pub fn deserialize<'de, D>(deserializer: D) -> Result<OffsetDateTime, D::Error>
1186        where
1187            D: serde::Deserializer<'de>,
1188        {
1189            let value = <String as Deserialize>::deserialize(deserializer)?;
1190            let value = value.parse::<i64>().map_err(DeError::custom)?;
1191            OffsetDateTime::from_unix_timestamp(value).map_err(DeError::custom)
1192        }
1193
1194        pub fn serialize_none_if<S>(
1195            value: &Option<OffsetDateTime>,
1196            none_value: &str,
1197            serializer: S,
1198        ) -> Result<S::Ok, S::Error>
1199        where
1200            S: serde::Serializer,
1201        {
1202            super::serialize_option_or_sentinel(value.as_ref(), none_value, serializer, serialize)
1203        }
1204
1205        pub fn deserialize_none_if<'de, D>(
1206            deserializer: D,
1207            none_if: &[&str],
1208        ) -> Result<Option<OffsetDateTime>, D::Error>
1209        where
1210            D: serde::Deserializer<'de>,
1211        {
1212            super::deserialize_none_if_with(deserializer, none_if, |value| {
1213                let value = value.parse::<i64>().map_err(|error| error.to_string())?;
1214                OffsetDateTime::from_unix_timestamp(value).map_err(|error| error.to_string())
1215            })
1216        }
1217
1218        pub mod option {
1219            use serde::Deserialize;
1220            use serde::de::Error as DeError;
1221
1222            use super::super as serde_string;
1223            use crate::OffsetDateTime;
1224
1225            pub fn serialize<S>(
1226                value: &Option<OffsetDateTime>,
1227                serializer: S,
1228            ) -> Result<S::Ok, S::Error>
1229            where
1230                S: serde::Serializer,
1231            {
1232                match value {
1233                    Some(value) => super::serialize(value, serializer),
1234                    None => serializer.serialize_none(),
1235                }
1236            }
1237
1238            pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<OffsetDateTime>, D::Error>
1239            where
1240                D: serde::Deserializer<'de>,
1241            {
1242                let value = <Option<String> as Deserialize>::deserialize(deserializer)?;
1243                value
1244                    .map(|value| {
1245                        let value = value.parse::<i64>().map_err(DeError::custom)?;
1246                        OffsetDateTime::from_unix_timestamp(value).map_err(DeError::custom)
1247                    })
1248                    .transpose()
1249            }
1250
1251            pub fn deserialize_none_if<'de, D>(
1252                deserializer: D,
1253                none_if: &[&str],
1254            ) -> Result<Option<OffsetDateTime>, D::Error>
1255            where
1256                D: serde::Deserializer<'de>,
1257            {
1258                serde_string::deserialize_option_none_if_with(deserializer, none_if, |value| {
1259                    let value = value.parse::<i64>().map_err(|error| error.to_string())?;
1260                    OffsetDateTime::from_unix_timestamp(value).map_err(|error| error.to_string())
1261                })
1262            }
1263        }
1264    }
1265
1266    pub mod as_time {
1267        use serde::Deserialize;
1268        use serde::de::Error as DeError;
1269
1270        use crate::Time;
1271
1272        pub fn serialize<S>(value: &Time, serializer: S) -> Result<S::Ok, S::Error>
1273        where
1274            S: serde::Serializer,
1275        {
1276            serializer.serialize_str(&crate::format_time(value))
1277        }
1278
1279        pub fn deserialize<'de, D>(deserializer: D) -> Result<Time, D::Error>
1280        where
1281            D: serde::Deserializer<'de>,
1282        {
1283            let value = <String as Deserialize>::deserialize(deserializer)?;
1284            crate::parse_time(&value).map_err(DeError::custom)
1285        }
1286
1287        pub fn serialize_none_if<S>(
1288            value: &Option<Time>,
1289            none_value: &str,
1290            serializer: S,
1291        ) -> Result<S::Ok, S::Error>
1292        where
1293            S: serde::Serializer,
1294        {
1295            super::serialize_option_or_sentinel(value.as_ref(), none_value, serializer, serialize)
1296        }
1297
1298        pub fn deserialize_none_if<'de, D>(
1299            deserializer: D,
1300            none_if: &[&str],
1301        ) -> Result<Option<Time>, D::Error>
1302        where
1303            D: serde::Deserializer<'de>,
1304        {
1305            super::deserialize_none_if_with(deserializer, none_if, crate::parse_time)
1306        }
1307
1308        pub mod option {
1309            use serde::Deserialize;
1310            use serde::de::Error as DeError;
1311
1312            use crate::Time;
1313
1314            pub fn serialize<S>(value: &Option<Time>, serializer: S) -> Result<S::Ok, S::Error>
1315            where
1316                S: serde::Serializer,
1317            {
1318                match value {
1319                    Some(value) => super::serialize(value, serializer),
1320                    None => serializer.serialize_none(),
1321                }
1322            }
1323
1324            pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Time>, D::Error>
1325            where
1326                D: serde::Deserializer<'de>,
1327            {
1328                let value = <Option<String> as Deserialize>::deserialize(deserializer)?;
1329                let Some(value) = value else {
1330                    return Ok(None);
1331                };
1332                let value = value.trim();
1333                if value.is_empty() {
1334                    return Ok(None);
1335                }
1336                crate::parse_time(value).map(Some).map_err(DeError::custom)
1337            }
1338
1339            pub fn deserialize_none_if<'de, D>(
1340                deserializer: D,
1341                none_if: &[&str],
1342            ) -> Result<Option<Time>, D::Error>
1343            where
1344                D: serde::Deserializer<'de>,
1345            {
1346                let value = <Option<String> as Deserialize>::deserialize(deserializer)?;
1347                let Some(value) = value else {
1348                    return Ok(None);
1349                };
1350                if none_if.contains(&value.as_str()) {
1351                    return Ok(None);
1352                }
1353                let value = value.trim();
1354                if value.is_empty() {
1355                    return Ok(None);
1356                }
1357                crate::parse_time(value).map(Some).map_err(DeError::custom)
1358            }
1359        }
1360    }
1361
1362    fn serialize_display<T, S>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
1363    where
1364        T: fmt::Display,
1365        S: serde::Serializer,
1366    {
1367        serializer.serialize_str(&value.to_string())
1368    }
1369
1370    fn serialize_option_display<T, S>(value: Option<&T>, serializer: S) -> Result<S::Ok, S::Error>
1371    where
1372        T: fmt::Display,
1373        S: serde::Serializer,
1374    {
1375        match value {
1376            Some(value) => serialize_display(value, serializer),
1377            None => serializer.serialize_none(),
1378        }
1379    }
1380
1381    fn serialize_option_or_sentinel<T, S>(
1382        value: Option<&T>,
1383        none_value: &str,
1384        serializer: S,
1385        serialize: impl FnOnce(&T, S) -> Result<S::Ok, S::Error>,
1386    ) -> Result<S::Ok, S::Error>
1387    where
1388        S: serde::Serializer,
1389    {
1390        match value {
1391            Some(value) => serialize(value, serializer),
1392            None => serializer.serialize_str(none_value),
1393        }
1394    }
1395
1396    fn deserialize_from_str<'de, T, D>(deserializer: D) -> Result<T, D::Error>
1397    where
1398        T: FromStr,
1399        T::Err: fmt::Display,
1400        D: serde::Deserializer<'de>,
1401    {
1402        let value = String::deserialize(deserializer)?;
1403        value.parse::<T>().map_err(DeError::custom)
1404    }
1405
1406    fn deserialize_option_from_str<'de, T, D>(deserializer: D) -> Result<Option<T>, D::Error>
1407    where
1408        T: FromStr,
1409        T::Err: fmt::Display,
1410        D: serde::Deserializer<'de>,
1411    {
1412        let value = Option::<String>::deserialize(deserializer)?;
1413        value
1414            .map(|value| value.parse::<T>().map_err(DeError::custom))
1415            .transpose()
1416    }
1417
1418    fn deserialize_none_if_with<'de, T, D, E>(
1419        deserializer: D,
1420        none_if: &[&str],
1421        parse: impl FnOnce(&str) -> Result<T, E>,
1422    ) -> Result<Option<T>, D::Error>
1423    where
1424        D: serde::Deserializer<'de>,
1425        E: fmt::Display,
1426    {
1427        let value = String::deserialize(deserializer)?;
1428        parse_none_if_with(&value, none_if, parse)
1429    }
1430
1431    fn deserialize_option_none_if_with<'de, T, D, E>(
1432        deserializer: D,
1433        none_if: &[&str],
1434        parse: impl FnOnce(&str) -> Result<T, E>,
1435    ) -> Result<Option<T>, D::Error>
1436    where
1437        D: serde::Deserializer<'de>,
1438        E: fmt::Display,
1439    {
1440        let Some(value) = Option::<String>::deserialize(deserializer)? else {
1441            return Ok(None);
1442        };
1443        parse_none_if_with(&value, none_if, parse)
1444    }
1445
1446    fn parse_none_if_with<T, E, D>(
1447        value: &str,
1448        none_if: &[&str],
1449        parse: impl FnOnce(&str) -> Result<T, E>,
1450    ) -> Result<Option<T>, D>
1451    where
1452        E: fmt::Display,
1453        D: DeError,
1454    {
1455        if none_if.contains(&value) {
1456            return Ok(None);
1457        }
1458        parse(value).map(Some).map_err(DeError::custom)
1459    }
1460
1461    fn deserialize_bool(value: &str) -> Result<bool, &'static str> {
1462        match value {
1463            "1" => Ok(true),
1464            "0" => Ok(false),
1465            value if value.eq_ignore_ascii_case("true") => Ok(true),
1466            value if value.eq_ignore_ascii_case("false") => Ok(false),
1467            _ => Err("invalid boolean string"),
1468        }
1469    }
1470}
1471
1472#[cfg(feature = "serde")]
1473#[allow(clippy::missing_errors_doc)]
1474pub mod serde_integer {
1475    pub mod as_unix_time {
1476        use serde::Deserialize;
1477        use serde::de::Error as DeError;
1478
1479        use crate::OffsetDateTime;
1480
1481        pub fn serialize<S>(value: &OffsetDateTime, serializer: S) -> Result<S::Ok, S::Error>
1482        where
1483            S: serde::Serializer,
1484        {
1485            serializer.serialize_i64(value.unix_timestamp())
1486        }
1487
1488        pub fn deserialize<'de, D>(deserializer: D) -> Result<OffsetDateTime, D::Error>
1489        where
1490            D: serde::Deserializer<'de>,
1491        {
1492            let value = <i64 as Deserialize>::deserialize(deserializer)?;
1493            OffsetDateTime::from_unix_timestamp(value).map_err(DeError::custom)
1494        }
1495
1496        pub mod option {
1497            use serde::Deserialize;
1498            use serde::de::Error as DeError;
1499
1500            use crate::OffsetDateTime;
1501
1502            pub fn serialize<S>(
1503                value: &Option<OffsetDateTime>,
1504                serializer: S,
1505            ) -> Result<S::Ok, S::Error>
1506            where
1507                S: serde::Serializer,
1508            {
1509                match value {
1510                    Some(value) => super::serialize(value, serializer),
1511                    None => serializer.serialize_none(),
1512                }
1513            }
1514
1515            pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<OffsetDateTime>, D::Error>
1516            where
1517                D: serde::Deserializer<'de>,
1518            {
1519                let value = <Option<i64> as Deserialize>::deserialize(deserializer)?;
1520                value
1521                    .map(|value| {
1522                        OffsetDateTime::from_unix_timestamp(value).map_err(DeError::custom)
1523                    })
1524                    .transpose()
1525            }
1526        }
1527    }
1528
1529    pub mod as_bool {
1530        use crate::serde_string::as_bool as string_bool;
1531
1532        pub fn serialize<S>(value: &bool, serializer: S) -> Result<S::Ok, S::Error>
1533        where
1534            S: serde::Serializer,
1535        {
1536            serializer.serialize_u8(u8::from(*value))
1537        }
1538
1539        pub fn deserialize<'de, D>(deserializer: D) -> Result<bool, D::Error>
1540        where
1541            D: serde::Deserializer<'de>,
1542        {
1543            string_bool::deserialize(deserializer)
1544        }
1545
1546        pub mod option {
1547            use crate::serde_string::as_bool::option as string_bool_option;
1548
1549            pub fn serialize<S>(value: &Option<bool>, serializer: S) -> Result<S::Ok, S::Error>
1550            where
1551                S: serde::Serializer,
1552            {
1553                match value {
1554                    Some(value) => super::serialize(value, serializer),
1555                    None => serializer.serialize_none(),
1556                }
1557            }
1558
1559            pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<bool>, D::Error>
1560            where
1561                D: serde::Deserializer<'de>,
1562            {
1563                string_bool_option::deserialize(deserializer)
1564            }
1565        }
1566    }
1567}
1568
1569#[cfg(feature = "json")]
1570pub mod treat_error_as_none {
1571    use serde::de::DeserializeOwned;
1572    use serde::{Deserialize, Deserializer, Serialize, Serializer};
1573
1574    /// Serializes an optional value, omitting `None`.
1575    ///
1576    /// # Errors
1577    ///
1578    /// Returns an error if serializing the inner value fails.
1579    pub fn serialize<S, T>(value: &Option<T>, serializer: S) -> Result<S::Ok, S::Error>
1580    where
1581        S: Serializer,
1582        T: Serialize,
1583    {
1584        match value {
1585            Some(inner) => inner.serialize(serializer),
1586            None => serializer.serialize_none(),
1587        }
1588    }
1589
1590    /// Deserializes an optional value, treating invalid inner values as `None`.
1591    ///
1592    /// # Errors
1593    ///
1594    /// Returns an error if the input cannot be read as JSON.
1595    pub fn deserialize<'de, D, T>(deserializer: D) -> Result<Option<T>, D::Error>
1596    where
1597        D: Deserializer<'de>,
1598        T: DeserializeOwned,
1599    {
1600        let value = serde_json::Value::deserialize(deserializer)?;
1601        match T::deserialize(value) {
1602            Ok(parsed) => Ok(Some(parsed)),
1603            Err(_) => Ok(None),
1604        }
1605    }
1606}
1607
1608/// Inserts a header into a header map.
1609///
1610/// # Errors
1611///
1612/// Returns an error if the header name or value is invalid.
1613pub fn insert_header(
1614    headers: &mut http::HeaderMap,
1615    name: &'static str,
1616    value: &str,
1617) -> Result<(), Error> {
1618    headers.insert(
1619        HeaderName::from_bytes(name.as_bytes())?,
1620        HeaderValue::from_str(value)?,
1621    );
1622    Ok(())
1623}
1624
1625#[must_use]
1626pub fn has_json_content_type(headers: &http::HeaderMap) -> bool {
1627    headers
1628        .get(CONTENT_TYPE)
1629        .and_then(|value| value.to_str().ok())
1630        .is_some_and(is_json_media_type)
1631}
1632
1633fn is_json_media_type(value: &str) -> bool {
1634    let media_type = value.split(';').next().unwrap_or(value).trim();
1635    if media_type.eq_ignore_ascii_case("application/json") {
1636        return true;
1637    }
1638
1639    let Some((_, subtype)) = media_type.rsplit_once('/') else {
1640        return false;
1641    };
1642    ends_with_ignore_ascii_case(subtype, "+json")
1643}
1644
1645fn ends_with_ignore_ascii_case(value: &str, suffix: &str) -> bool {
1646    let value = value.as_bytes();
1647    let suffix = suffix.as_bytes();
1648    value.len() >= suffix.len() && value[value.len() - suffix.len()..].eq_ignore_ascii_case(suffix)
1649}
1650
1651fn append_percent_encoded(out: &mut String, bytes: &[u8]) {
1652    const HEX: &[u8; 16] = b"0123456789ABCDEF";
1653
1654    for &byte in bytes {
1655        if is_unreserved(byte) {
1656            out.push(byte as char);
1657        } else {
1658            out.push('%');
1659            out.push(HEX[(byte >> 4) as usize] as char);
1660            out.push(HEX[(byte & 0x0f) as usize] as char);
1661        }
1662    }
1663}
1664
1665const fn is_unreserved(byte: u8) -> bool {
1666    matches!(
1667        byte,
1668        b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'.' | b'_' | b'~'
1669    )
1670}
1671
1672#[cfg(test)]
1673mod tests {
1674    use super::*;
1675
1676    #[cfg(all(feature = "serde", feature = "json"))]
1677    mod required_f64_none_if {
1678        use crate::serde_string::as_f64;
1679
1680        #[allow(clippy::ref_option)]
1681        pub fn serialize<S>(value: &Option<f64>, serializer: S) -> Result<S::Ok, S::Error>
1682        where
1683            S: serde::Serializer,
1684        {
1685            as_f64::serialize_none_if(value, "NA", serializer)
1686        }
1687
1688        pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<f64>, D::Error>
1689        where
1690            D: serde::Deserializer<'de>,
1691        {
1692            as_f64::deserialize_none_if(deserializer, &["NA", "-"])
1693        }
1694    }
1695
1696    #[cfg(all(feature = "serde", feature = "json"))]
1697    mod optional_f64_none_if {
1698        use crate::serde_string::as_f64::{self, option};
1699
1700        #[allow(clippy::ref_option)]
1701        pub fn serialize<S>(value: &Option<f64>, serializer: S) -> Result<S::Ok, S::Error>
1702        where
1703            S: serde::Serializer,
1704        {
1705            as_f64::serialize_none_if(value, "NA", serializer)
1706        }
1707
1708        pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<f64>, D::Error>
1709        where
1710            D: serde::Deserializer<'de>,
1711        {
1712            option::deserialize_none_if(deserializer, &["NA", "-"])
1713        }
1714    }
1715
1716    #[test]
1717    fn encodes_path_segments() {
1718        let mut out = String::new();
1719        append_path_segment(&mut out, "a/b c");
1720        assert_eq!(out, "a%2Fb%20c");
1721    }
1722
1723    #[test]
1724    fn appends_query_pairs() {
1725        let mut out = String::from("/pets");
1726        let mut first = true;
1727        append_query_pair(&mut out, &mut first, "tag name", "small/dog");
1728        append_query_pair(&mut out, &mut first, "limit", "10");
1729        assert_eq!(out, "/pets?tag%20name=small%2Fdog&limit=10");
1730    }
1731
1732    #[test]
1733    fn parses_range_strings() {
1734        assert_eq!(parse_range::<u8>("14-17").unwrap(), (Some(14), Some(17)));
1735        assert_eq!(parse_range::<u8>("14-").unwrap(), (Some(14), None));
1736        assert_eq!(parse_range::<u8>("-17").unwrap(), (None, Some(17)));
1737        assert_eq!(parse_range::<u8>("").unwrap(), (None, None));
1738        assert!(matches!(
1739            parse_range::<u8>("14-17-20"),
1740            Err(ParseRangeError::TooManySeparators)
1741        ));
1742    }
1743
1744    #[test]
1745    fn formats_range_strings() {
1746        assert_eq!(format_range(&Some(14), &Some(17)), "14-17");
1747        assert_eq!(format_range(&Some(14), &None::<u8>), "14-");
1748        assert_eq!(format_range(&None::<u8>, &Some(17)), "-17");
1749        assert_eq!(format_range(&None::<u8>, &None::<u8>), "");
1750    }
1751
1752    #[test]
1753    fn parses_and_formats_time_strings() {
1754        let time = parse_time("0620").unwrap();
1755        assert_eq!(time.hour(), 6);
1756        assert_eq!(time.minute(), 20);
1757        assert_eq!(format_time(&time), "0620");
1758        assert_eq!(parse_time("6:20"), Err(ParseTimeError::InvalidFormat));
1759        assert_eq!(parse_time("2400"), Err(ParseTimeError::ComponentRange));
1760    }
1761
1762    #[test]
1763    fn parses_and_formats_date_strings() {
1764        let date = parse_date("2024-07-16").unwrap();
1765        assert_eq!(
1766            date,
1767            Date::from_calendar_date(2024, Month::July, 16).unwrap()
1768        );
1769        assert_eq!(format_date(&date), "2024-07-16");
1770        assert_eq!(parse_date("2024-7-16"), Err(ParseDateError::InvalidFormat));
1771        assert_eq!(parse_date("not-a-date"), Err(ParseDateError::InvalidFormat));
1772    }
1773
1774    #[test]
1775    fn parses_and_formats_naive_datetime_strings() {
1776        let datetime = parse_naive_datetime("2024-07-16T23:59:00").unwrap();
1777        assert_eq!(datetime.hour(), 23);
1778        assert_eq!(datetime.minute(), 59);
1779        assert_eq!(datetime.second(), 0);
1780        assert_eq!(format_naive_datetime(&datetime), "2024-07-16T23:59:00");
1781        assert_eq!(
1782            parse_naive_datetime("2024-07-16T23:59"),
1783            Err(ParseNaiveDateTimeError::InvalidFormat)
1784        );
1785        assert_eq!(
1786            parse_naive_datetime("2024-07-16 23:59:00"),
1787            Err(ParseNaiveDateTimeError::InvalidFormat)
1788        );
1789    }
1790
1791    #[test]
1792    fn formats_unix_time_seconds() {
1793        let datetime = OffsetDateTime::from_unix_timestamp(-1).unwrap();
1794        assert_eq!(format_unix_time(&datetime), "-1");
1795    }
1796
1797    #[cfg(all(feature = "serde", feature = "json"))]
1798    #[test]
1799    fn serde_string_naive_datetime_round_trips() {
1800        #[derive(serde::Deserialize, serde::Serialize)]
1801        struct Value {
1802            #[serde(with = "crate::serde_string::as_naive_datetime")]
1803            at: PrimitiveDateTime,
1804        }
1805
1806        let at = parse_naive_datetime("2024-07-16T23:59:00").unwrap();
1807        let encoded = serde_json::to_value(Value { at }).unwrap();
1808        assert_eq!(encoded, serde_json::json!({ "at": "2024-07-16T23:59:00" }));
1809
1810        let decoded = serde_json::from_value::<Value>(encoded).unwrap();
1811        assert_eq!(decoded.at, at);
1812    }
1813
1814    #[cfg(all(feature = "serde", feature = "json"))]
1815    #[test]
1816    fn serde_string_date_round_trips() {
1817        #[derive(serde::Deserialize, serde::Serialize)]
1818        struct Value {
1819            #[serde(with = "crate::serde_string::as_date")]
1820            day: Date,
1821        }
1822
1823        let date = Date::from_calendar_date(2024, Month::July, 16).unwrap();
1824        let encoded = serde_json::to_value(Value { day: date }).unwrap();
1825        assert_eq!(encoded, serde_json::json!({ "day": "2024-07-16" }));
1826
1827        let decoded = serde_json::from_value::<Value>(encoded).unwrap();
1828        assert_eq!(decoded.day, date);
1829    }
1830
1831    #[test]
1832    fn recognizes_json_content_types() {
1833        let mut headers = http::HeaderMap::new();
1834        headers.insert(
1835            CONTENT_TYPE,
1836            http::HeaderValue::from_static("application/problem+json; charset=utf-8"),
1837        );
1838        assert!(has_json_content_type(&headers));
1839    }
1840
1841    #[test]
1842    fn response_parts_holds_status_headers_body() {
1843        let mut headers = http::HeaderMap::new();
1844        headers.insert(
1845            CONTENT_TYPE,
1846            http::HeaderValue::from_static("application/json"),
1847        );
1848        let body = br#"{"ok":true}"#.to_vec();
1849        let parts = ResponseParts {
1850            status: http::StatusCode::OK,
1851            headers,
1852            body,
1853        };
1854        assert_eq!(parts.status, http::StatusCode::OK);
1855        assert_eq!(parts.headers.get(CONTENT_TYPE).unwrap(), "application/json");
1856        assert_eq!(parts.body, br#"{"ok":true}"#);
1857    }
1858
1859    #[cfg(all(feature = "serde", feature = "json"))]
1860    #[test]
1861    fn serde_string_bool_accepts_string_and_numeric_values() {
1862        #[derive(serde::Deserialize, serde::Serialize)]
1863        struct Value {
1864            #[serde(with = "crate::serde_string::as_bool")]
1865            monitored: bool,
1866        }
1867
1868        let numeric = serde_json::from_str::<Value>(r#"{"monitored":0}"#).unwrap();
1869        assert!(!numeric.monitored);
1870
1871        let string = serde_json::from_str::<Value>(r#"{"monitored":"1"}"#).unwrap();
1872        assert!(string.monitored);
1873
1874        let encoded = serde_json::to_value(Value { monitored: false }).unwrap();
1875        assert_eq!(encoded, serde_json::json!({ "monitored": "0" }));
1876    }
1877
1878    #[cfg(all(feature = "serde", feature = "json"))]
1879    #[test]
1880    fn serde_string_none_if_is_strict_and_canonical() {
1881        #[derive(Debug, serde::Deserialize, serde::Serialize)]
1882        struct Value {
1883            #[serde(with = "required_f64_none_if")]
1884            required: Option<f64>,
1885            #[serde(default, with = "optional_f64_none_if")]
1886            optional: Option<f64>,
1887        }
1888
1889        let valid =
1890            serde_json::from_str::<Value>(r#"{"required":"28.7","optional":"10.5"}"#).unwrap();
1891        assert_eq!(valid.required, Some(28.7));
1892        assert_eq!(valid.optional, Some(10.5));
1893
1894        let sentinel =
1895            serde_json::from_str::<Value>(r#"{"required":"-","optional":"NA"}"#).unwrap();
1896        assert_eq!(sentinel.required, None);
1897        assert_eq!(sentinel.optional, None);
1898
1899        let null_optional =
1900            serde_json::from_str::<Value>(r#"{"required":"28.7","optional":null}"#).unwrap();
1901        assert_eq!(null_optional.optional, None);
1902        assert!(serde_json::from_str::<Value>(r#"{"required":null}"#).is_err());
1903        assert!(serde_json::from_str::<Value>(r#"{"required":"unexpected"}"#).is_err());
1904
1905        let encoded = serde_json::to_value(Value {
1906            required: None,
1907            optional: Some(10.5),
1908        })
1909        .unwrap();
1910        assert_eq!(
1911            encoded,
1912            serde_json::json!({"required": "NA", "optional": "10.5"})
1913        );
1914    }
1915
1916    #[cfg(all(feature = "serde", feature = "json"))]
1917    #[test]
1918    fn serde_string_none_if_preserves_bool_and_time_parsers() {
1919        use crate::serde_string::{as_bool, as_time::option as time_option};
1920        use serde_json::Value;
1921
1922        let bool_sentinel =
1923            as_bool::deserialize_none_if(Value::String("NA".to_owned()), &["NA"]).unwrap();
1924        assert_eq!(bool_sentinel, None);
1925
1926        let bool_numeric = as_bool::deserialize_none_if(Value::from(1), &["NA"]).unwrap();
1927        assert_eq!(bool_numeric, Some(true));
1928
1929        let empty_time =
1930            time_option::deserialize_none_if(Value::String("  ".to_owned()), &["-"]).unwrap();
1931        assert_eq!(empty_time, None);
1932        assert!(
1933            time_option::deserialize_none_if(Value::String("invalid".to_owned()), &["-"]).is_err()
1934        );
1935    }
1936
1937    #[cfg(all(feature = "serde", feature = "json"))]
1938    #[test]
1939    fn serde_integer_bool_accepts_numeric_values() {
1940        #[derive(serde::Deserialize, serde::Serialize)]
1941        struct Value {
1942            #[serde(with = "crate::serde_integer::as_bool")]
1943            monitored: bool,
1944        }
1945
1946        let numeric = serde_json::from_str::<Value>(r#"{"monitored":0}"#).unwrap();
1947        assert!(!numeric.monitored);
1948
1949        let encoded = serde_json::to_value(Value { monitored: true }).unwrap();
1950        assert_eq!(encoded, serde_json::json!({ "monitored": 1 }));
1951    }
1952
1953    #[cfg(all(feature = "serde", feature = "json"))]
1954    #[test]
1955    fn serde_string_unix_time_round_trips() {
1956        #[derive(serde::Deserialize, serde::Serialize)]
1957        struct Value {
1958            #[serde(with = "crate::serde_string::as_unix_time")]
1959            at: OffsetDateTime,
1960        }
1961
1962        let at = OffsetDateTime::from_unix_timestamp(1_719_892_800).unwrap();
1963        let encoded = serde_json::to_value(Value { at }).unwrap();
1964        assert_eq!(encoded, serde_json::json!({ "at": "1719892800" }));
1965
1966        let decoded = serde_json::from_value::<Value>(encoded).unwrap();
1967        assert_eq!(decoded.at, at);
1968    }
1969
1970    #[cfg(all(feature = "serde", feature = "json"))]
1971    #[test]
1972    fn serde_integer_unix_time_round_trips_and_handles_null() {
1973        #[derive(serde::Deserialize, serde::Serialize)]
1974        struct Value {
1975            #[serde(with = "crate::serde_integer::as_unix_time")]
1976            at: OffsetDateTime,
1977            #[serde(with = "crate::serde_integer::as_unix_time::option")]
1978            maybe_at: Option<OffsetDateTime>,
1979        }
1980
1981        let at = OffsetDateTime::from_unix_timestamp(1_719_892_800).unwrap();
1982        let encoded = serde_json::to_value(Value { at, maybe_at: None }).unwrap();
1983        assert_eq!(
1984            encoded,
1985            serde_json::json!({ "at": 1_719_892_800, "maybe_at": null })
1986        );
1987
1988        let decoded = serde_json::from_value::<Value>(encoded).unwrap();
1989        assert_eq!(decoded.at, at);
1990        assert_eq!(decoded.maybe_at, None);
1991    }
1992
1993    #[cfg(all(feature = "serde", feature = "json"))]
1994    #[test]
1995    fn serde_integer_unix_time_rejects_out_of_range_values() {
1996        #[derive(serde::Deserialize)]
1997        #[allow(dead_code)]
1998        struct Value {
1999            #[serde(with = "crate::serde_integer::as_unix_time")]
2000            at: OffsetDateTime,
2001        }
2002
2003        assert!(serde_json::from_str::<Value>(r#"{"at":9223372036854775807}"#).is_err());
2004    }
2005}