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