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 serialize_mapped<S>(
738            value: &bool,
739            true_value: &str,
740            false_value: &str,
741            serializer: S,
742        ) -> Result<S::Ok, S::Error>
743        where
744            S: serde::Serializer,
745        {
746            serializer.serialize_str(if *value { true_value } else { false_value })
747        }
748
749        pub fn serialize_mapped_none_if<S>(
750            value: &Option<bool>,
751            true_value: &str,
752            false_value: &str,
753            none_value: &str,
754            serializer: S,
755        ) -> Result<S::Ok, S::Error>
756        where
757            S: serde::Serializer,
758        {
759            super::serialize_option_or_sentinel(
760                value.as_ref(),
761                none_value,
762                serializer,
763                |value, serializer| serialize_mapped(value, true_value, false_value, serializer),
764            )
765        }
766
767        pub fn deserialize<'de, D>(deserializer: D) -> Result<bool, D::Error>
768        where
769            D: serde::Deserializer<'de>,
770        {
771            deserializer.deserialize_any(BoolVisitor)
772        }
773
774        pub fn deserialize_mapped<'de, D>(
775            deserializer: D,
776            true_values: &[&str],
777            false_values: &[&str],
778            unknown_as: Option<bool>,
779        ) -> Result<bool, D::Error>
780        where
781            D: serde::Deserializer<'de>,
782        {
783            deserializer.deserialize_any(MappedBoolVisitor {
784                true_values,
785                false_values,
786                unknown_as,
787            })
788        }
789
790        pub fn serialize_none_if<S>(
791            value: &Option<bool>,
792            none_value: &str,
793            serializer: S,
794        ) -> Result<S::Ok, S::Error>
795        where
796            S: serde::Serializer,
797        {
798            super::serialize_option_or_sentinel(value.as_ref(), none_value, serializer, serialize)
799        }
800
801        pub fn deserialize_none_if<'de, D>(
802            deserializer: D,
803            none_if: &[&str],
804        ) -> Result<Option<bool>, D::Error>
805        where
806            D: serde::Deserializer<'de>,
807        {
808            deserializer.deserialize_any(BoolNoneIfVisitor { none_if })
809        }
810
811        pub fn deserialize_mapped_none_if<'de, D>(
812            deserializer: D,
813            true_values: &[&str],
814            false_values: &[&str],
815            unknown_as: Option<bool>,
816            none_if: &[&str],
817        ) -> Result<Option<bool>, D::Error>
818        where
819            D: serde::Deserializer<'de>,
820        {
821            deserializer.deserialize_any(MappedBoolNoneIfVisitor {
822                true_values,
823                false_values,
824                unknown_as,
825                none_if,
826            })
827        }
828
829        struct BoolVisitor;
830
831        impl Visitor<'_> for BoolVisitor {
832            type Value = bool;
833
834            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
835                formatter.write_str("a boolean string or numeric boolean")
836            }
837
838            fn visit_bool<E>(self, value: bool) -> Result<Self::Value, E> {
839                Ok(value)
840            }
841
842            fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
843            where
844                E: DeError,
845            {
846                super::deserialize_bool(value).map_err(DeError::custom)
847            }
848
849            fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
850            where
851                E: DeError,
852            {
853                self.visit_str(&value)
854            }
855
856            fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E>
857            where
858                E: DeError,
859            {
860                match value {
861                    0 => Ok(false),
862                    1 => Ok(true),
863                    _ => Err(DeError::custom("invalid boolean number")),
864                }
865            }
866
867            fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
868            where
869                E: DeError,
870            {
871                match value {
872                    0 => Ok(false),
873                    1 => Ok(true),
874                    _ => Err(DeError::custom("invalid boolean number")),
875                }
876            }
877        }
878
879        struct MappedBoolVisitor<'a> {
880            true_values: &'a [&'a str],
881            false_values: &'a [&'a str],
882            unknown_as: Option<bool>,
883        }
884
885        impl Visitor<'_> for MappedBoolVisitor<'_> {
886            type Value = bool;
887
888            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
889                formatter.write_str("a configured boolean string or numeric boolean")
890            }
891
892            fn visit_bool<E>(self, value: bool) -> Result<Self::Value, E> {
893                Ok(value)
894            }
895
896            fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
897            where
898                E: DeError,
899            {
900                mapped_bool(value, self.true_values, self.false_values, self.unknown_as)
901                    .ok_or_else(|| DeError::custom("invalid mapped boolean string"))
902            }
903
904            fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
905            where
906                E: DeError,
907            {
908                self.visit_str(&value)
909            }
910
911            fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E>
912            where
913                E: DeError,
914            {
915                match value {
916                    0 => Ok(false),
917                    1 => Ok(true),
918                    _ => Err(DeError::custom("invalid boolean number")),
919                }
920            }
921
922            fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
923            where
924                E: DeError,
925            {
926                match value {
927                    0 => Ok(false),
928                    1 => Ok(true),
929                    _ => Err(DeError::custom("invalid boolean number")),
930                }
931            }
932        }
933
934        struct BoolNoneIfVisitor<'a> {
935            none_if: &'a [&'a str],
936        }
937
938        impl Visitor<'_> for BoolNoneIfVisitor<'_> {
939            type Value = Option<bool>;
940
941            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
942                formatter.write_str("a boolean string, numeric boolean, or configured sentinel")
943            }
944
945            fn visit_bool<E>(self, value: bool) -> Result<Self::Value, E> {
946                Ok(Some(value))
947            }
948
949            fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
950            where
951                E: DeError,
952            {
953                if self.none_if.contains(&value) {
954                    Ok(None)
955                } else {
956                    super::deserialize_bool(value)
957                        .map(Some)
958                        .map_err(DeError::custom)
959                }
960            }
961
962            fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
963            where
964                E: DeError,
965            {
966                self.visit_str(&value)
967            }
968
969            fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E>
970            where
971                E: DeError,
972            {
973                match value {
974                    0 => Ok(Some(false)),
975                    1 => Ok(Some(true)),
976                    _ => Err(DeError::custom("invalid boolean number")),
977                }
978            }
979
980            fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
981            where
982                E: DeError,
983            {
984                match value {
985                    0 => Ok(Some(false)),
986                    1 => Ok(Some(true)),
987                    _ => Err(DeError::custom("invalid boolean number")),
988                }
989            }
990        }
991
992        struct MappedBoolNoneIfVisitor<'a> {
993            true_values: &'a [&'a str],
994            false_values: &'a [&'a str],
995            unknown_as: Option<bool>,
996            none_if: &'a [&'a str],
997        }
998
999        impl Visitor<'_> for MappedBoolNoneIfVisitor<'_> {
1000            type Value = Option<bool>;
1001
1002            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1003                formatter.write_str(
1004                    "a configured boolean string, numeric boolean, or configured sentinel",
1005                )
1006            }
1007
1008            fn visit_bool<E>(self, value: bool) -> Result<Self::Value, E> {
1009                Ok(Some(value))
1010            }
1011
1012            fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
1013            where
1014                E: DeError,
1015            {
1016                if self.none_if.contains(&value) {
1017                    Ok(None)
1018                } else {
1019                    mapped_bool(value, self.true_values, self.false_values, self.unknown_as)
1020                        .map(Some)
1021                        .ok_or_else(|| DeError::custom("invalid mapped boolean string"))
1022                }
1023            }
1024
1025            fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
1026            where
1027                E: DeError,
1028            {
1029                self.visit_str(&value)
1030            }
1031
1032            fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E>
1033            where
1034                E: DeError,
1035            {
1036                match value {
1037                    0 => Ok(Some(false)),
1038                    1 => Ok(Some(true)),
1039                    _ => Err(DeError::custom("invalid boolean number")),
1040                }
1041            }
1042
1043            fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
1044            where
1045                E: DeError,
1046            {
1047                match value {
1048                    0 => Ok(Some(false)),
1049                    1 => Ok(Some(true)),
1050                    _ => Err(DeError::custom("invalid boolean number")),
1051                }
1052            }
1053        }
1054
1055        pub mod option {
1056            use std::fmt;
1057
1058            use serde::de::Visitor;
1059
1060            pub fn serialize<S>(value: &Option<bool>, serializer: S) -> Result<S::Ok, S::Error>
1061            where
1062                S: serde::Serializer,
1063            {
1064                match value {
1065                    Some(value) => super::serialize(value, serializer),
1066                    None => serializer.serialize_none(),
1067                }
1068            }
1069
1070            pub fn serialize_mapped<S>(
1071                value: &Option<bool>,
1072                true_value: &str,
1073                false_value: &str,
1074                serializer: S,
1075            ) -> Result<S::Ok, S::Error>
1076            where
1077                S: serde::Serializer,
1078            {
1079                match value {
1080                    Some(value) => {
1081                        super::serialize_mapped(value, true_value, false_value, serializer)
1082                    }
1083                    None => serializer.serialize_none(),
1084                }
1085            }
1086
1087            pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<bool>, D::Error>
1088            where
1089                D: serde::Deserializer<'de>,
1090            {
1091                deserializer.deserialize_option(BoolOptionVisitor)
1092            }
1093
1094            pub fn deserialize_mapped<'de, D>(
1095                deserializer: D,
1096                true_values: &[&str],
1097                false_values: &[&str],
1098                unknown_as: Option<bool>,
1099            ) -> Result<Option<bool>, D::Error>
1100            where
1101                D: serde::Deserializer<'de>,
1102            {
1103                deserializer.deserialize_option(MappedBoolOptionVisitor {
1104                    true_values,
1105                    false_values,
1106                    unknown_as,
1107                    none_if: None,
1108                })
1109            }
1110
1111            pub fn deserialize_none_if<'de, D>(
1112                deserializer: D,
1113                none_if: &[&str],
1114            ) -> Result<Option<bool>, D::Error>
1115            where
1116                D: serde::Deserializer<'de>,
1117            {
1118                deserializer.deserialize_option(BoolOptionNoneIfVisitor { none_if })
1119            }
1120
1121            pub fn deserialize_mapped_none_if<'de, D>(
1122                deserializer: D,
1123                true_values: &[&str],
1124                false_values: &[&str],
1125                unknown_as: Option<bool>,
1126                none_if: &[&str],
1127            ) -> Result<Option<bool>, D::Error>
1128            where
1129                D: serde::Deserializer<'de>,
1130            {
1131                deserializer.deserialize_option(MappedBoolOptionVisitor {
1132                    true_values,
1133                    false_values,
1134                    unknown_as,
1135                    none_if: Some(none_if),
1136                })
1137            }
1138
1139            struct BoolOptionVisitor;
1140
1141            impl<'de> Visitor<'de> for BoolOptionVisitor {
1142                type Value = Option<bool>;
1143
1144                fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1145                    formatter.write_str("an optional boolean string or numeric boolean")
1146                }
1147
1148                fn visit_none<E>(self) -> Result<Self::Value, E> {
1149                    Ok(None)
1150                }
1151
1152                fn visit_unit<E>(self) -> Result<Self::Value, E> {
1153                    Ok(None)
1154                }
1155
1156                fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
1157                where
1158                    D: serde::Deserializer<'de>,
1159                {
1160                    super::deserialize(deserializer).map(Some)
1161                }
1162            }
1163
1164            struct BoolOptionNoneIfVisitor<'a> {
1165                none_if: &'a [&'a str],
1166            }
1167
1168            impl<'de> Visitor<'de> for BoolOptionNoneIfVisitor<'_> {
1169                type Value = Option<bool>;
1170
1171                fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1172                    formatter.write_str("an optional boolean string, numeric boolean, or sentinel")
1173                }
1174
1175                fn visit_none<E>(self) -> Result<Self::Value, E> {
1176                    Ok(None)
1177                }
1178
1179                fn visit_unit<E>(self) -> Result<Self::Value, E> {
1180                    Ok(None)
1181                }
1182
1183                fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
1184                where
1185                    D: serde::Deserializer<'de>,
1186                {
1187                    deserializer.deserialize_any(super::BoolNoneIfVisitor {
1188                        none_if: self.none_if,
1189                    })
1190                }
1191            }
1192
1193            struct MappedBoolOptionVisitor<'a> {
1194                true_values: &'a [&'a str],
1195                false_values: &'a [&'a str],
1196                unknown_as: Option<bool>,
1197                none_if: Option<&'a [&'a str]>,
1198            }
1199
1200            impl<'de> Visitor<'de> for MappedBoolOptionVisitor<'_> {
1201                type Value = Option<bool>;
1202
1203                fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1204                    formatter.write_str("an optional configured boolean string or numeric boolean")
1205                }
1206
1207                fn visit_none<E>(self) -> Result<Self::Value, E> {
1208                    Ok(None)
1209                }
1210
1211                fn visit_unit<E>(self) -> Result<Self::Value, E> {
1212                    Ok(None)
1213                }
1214
1215                fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
1216                where
1217                    D: serde::Deserializer<'de>,
1218                {
1219                    match self.none_if {
1220                        Some(none_if) => {
1221                            deserializer.deserialize_any(super::MappedBoolNoneIfVisitor {
1222                                true_values: self.true_values,
1223                                false_values: self.false_values,
1224                                unknown_as: self.unknown_as,
1225                                none_if,
1226                            })
1227                        }
1228                        None => super::deserialize_mapped(
1229                            deserializer,
1230                            self.true_values,
1231                            self.false_values,
1232                            self.unknown_as,
1233                        )
1234                        .map(Some),
1235                    }
1236                }
1237            }
1238        }
1239
1240        fn mapped_bool(
1241            value: &str,
1242            true_values: &[&str],
1243            false_values: &[&str],
1244            unknown_as: Option<bool>,
1245        ) -> Option<bool> {
1246            if true_values.contains(&value) {
1247                Some(true)
1248            } else if false_values.contains(&value) {
1249                Some(false)
1250            } else {
1251                unknown_as
1252            }
1253        }
1254    }
1255
1256    pub mod as_date {
1257        use serde::Deserialize;
1258        use serde::de::Error as DeError;
1259
1260        use crate::Date;
1261
1262        pub fn serialize<S>(value: &Date, serializer: S) -> Result<S::Ok, S::Error>
1263        where
1264            S: serde::Serializer,
1265        {
1266            serializer.serialize_str(&crate::format_date(value))
1267        }
1268
1269        pub fn deserialize<'de, D>(deserializer: D) -> Result<Date, D::Error>
1270        where
1271            D: serde::Deserializer<'de>,
1272        {
1273            let value = <String as Deserialize>::deserialize(deserializer)?;
1274            crate::parse_date(&value).map_err(DeError::custom)
1275        }
1276
1277        pub fn serialize_none_if<S>(
1278            value: &Option<Date>,
1279            none_value: &str,
1280            serializer: S,
1281        ) -> Result<S::Ok, S::Error>
1282        where
1283            S: serde::Serializer,
1284        {
1285            super::serialize_option_or_sentinel(value.as_ref(), none_value, serializer, serialize)
1286        }
1287
1288        pub fn deserialize_none_if<'de, D>(
1289            deserializer: D,
1290            none_if: &[&str],
1291        ) -> Result<Option<Date>, D::Error>
1292        where
1293            D: serde::Deserializer<'de>,
1294        {
1295            super::deserialize_none_if_with(deserializer, none_if, crate::parse_date)
1296        }
1297
1298        pub mod option {
1299            use serde::Deserialize;
1300            use serde::de::Error as DeError;
1301
1302            use super::super as serde_string;
1303            use crate::Date;
1304
1305            pub fn serialize<S>(value: &Option<Date>, serializer: S) -> Result<S::Ok, S::Error>
1306            where
1307                S: serde::Serializer,
1308            {
1309                match value {
1310                    Some(value) => super::serialize(value, serializer),
1311                    None => serializer.serialize_none(),
1312                }
1313            }
1314
1315            pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Date>, D::Error>
1316            where
1317                D: serde::Deserializer<'de>,
1318            {
1319                let value = <Option<String> as Deserialize>::deserialize(deserializer)?;
1320                value
1321                    .map(|value| crate::parse_date(&value).map_err(DeError::custom))
1322                    .transpose()
1323            }
1324
1325            pub fn deserialize_none_if<'de, D>(
1326                deserializer: D,
1327                none_if: &[&str],
1328            ) -> Result<Option<Date>, D::Error>
1329            where
1330                D: serde::Deserializer<'de>,
1331            {
1332                serde_string::deserialize_option_none_if_with(
1333                    deserializer,
1334                    none_if,
1335                    crate::parse_date,
1336                )
1337            }
1338        }
1339    }
1340
1341    pub mod as_naive_datetime {
1342        use serde::Deserialize;
1343        use serde::de::Error as DeError;
1344
1345        use crate::PrimitiveDateTime;
1346
1347        pub fn serialize<S>(value: &PrimitiveDateTime, serializer: S) -> Result<S::Ok, S::Error>
1348        where
1349            S: serde::Serializer,
1350        {
1351            serializer.serialize_str(&crate::format_naive_datetime(value))
1352        }
1353
1354        pub fn deserialize<'de, D>(deserializer: D) -> Result<PrimitiveDateTime, D::Error>
1355        where
1356            D: serde::Deserializer<'de>,
1357        {
1358            let value = <String as Deserialize>::deserialize(deserializer)?;
1359            crate::parse_naive_datetime(&value).map_err(DeError::custom)
1360        }
1361
1362        pub fn serialize_none_if<S>(
1363            value: &Option<PrimitiveDateTime>,
1364            none_value: &str,
1365            serializer: S,
1366        ) -> Result<S::Ok, S::Error>
1367        where
1368            S: serde::Serializer,
1369        {
1370            super::serialize_option_or_sentinel(value.as_ref(), none_value, serializer, serialize)
1371        }
1372
1373        pub fn deserialize_none_if<'de, D>(
1374            deserializer: D,
1375            none_if: &[&str],
1376        ) -> Result<Option<PrimitiveDateTime>, D::Error>
1377        where
1378            D: serde::Deserializer<'de>,
1379        {
1380            super::deserialize_none_if_with(deserializer, none_if, crate::parse_naive_datetime)
1381        }
1382
1383        pub mod option {
1384            use serde::Deserialize;
1385            use serde::de::Error as DeError;
1386
1387            use super::super as serde_string;
1388            use crate::PrimitiveDateTime;
1389
1390            pub fn serialize<S>(
1391                value: &Option<PrimitiveDateTime>,
1392                serializer: S,
1393            ) -> Result<S::Ok, S::Error>
1394            where
1395                S: serde::Serializer,
1396            {
1397                match value {
1398                    Some(value) => super::serialize(value, serializer),
1399                    None => serializer.serialize_none(),
1400                }
1401            }
1402
1403            pub fn deserialize<'de, D>(
1404                deserializer: D,
1405            ) -> Result<Option<PrimitiveDateTime>, D::Error>
1406            where
1407                D: serde::Deserializer<'de>,
1408            {
1409                let value = <Option<String> as Deserialize>::deserialize(deserializer)?;
1410                value
1411                    .map(|value| crate::parse_naive_datetime(&value).map_err(DeError::custom))
1412                    .transpose()
1413            }
1414
1415            pub fn deserialize_none_if<'de, D>(
1416                deserializer: D,
1417                none_if: &[&str],
1418            ) -> Result<Option<PrimitiveDateTime>, D::Error>
1419            where
1420                D: serde::Deserializer<'de>,
1421            {
1422                serde_string::deserialize_option_none_if_with(
1423                    deserializer,
1424                    none_if,
1425                    crate::parse_naive_datetime,
1426                )
1427            }
1428        }
1429    }
1430
1431    pub mod as_offset_datetime {
1432        use serde::Deserialize;
1433        use serde::de::Error as DeError;
1434        use serde::ser::Error as SerError;
1435
1436        use crate::OffsetDateTime;
1437        use time::format_description::well_known::Rfc3339;
1438
1439        pub fn serialize<S>(value: &OffsetDateTime, serializer: S) -> Result<S::Ok, S::Error>
1440        where
1441            S: serde::Serializer,
1442        {
1443            let value = value.format(&Rfc3339).map_err(SerError::custom)?;
1444            serializer.serialize_str(&value)
1445        }
1446
1447        pub fn deserialize<'de, D>(deserializer: D) -> Result<OffsetDateTime, D::Error>
1448        where
1449            D: serde::Deserializer<'de>,
1450        {
1451            let value = <String as Deserialize>::deserialize(deserializer)?;
1452            OffsetDateTime::parse(&value, &Rfc3339).map_err(DeError::custom)
1453        }
1454
1455        pub fn serialize_none_if<S>(
1456            value: &Option<OffsetDateTime>,
1457            none_value: &str,
1458            serializer: S,
1459        ) -> Result<S::Ok, S::Error>
1460        where
1461            S: serde::Serializer,
1462        {
1463            super::serialize_option_or_sentinel(value.as_ref(), none_value, serializer, serialize)
1464        }
1465
1466        pub fn deserialize_none_if<'de, D>(
1467            deserializer: D,
1468            none_if: &[&str],
1469        ) -> Result<Option<OffsetDateTime>, D::Error>
1470        where
1471            D: serde::Deserializer<'de>,
1472        {
1473            super::deserialize_none_if_with(deserializer, none_if, |value| {
1474                OffsetDateTime::parse(value, &Rfc3339)
1475            })
1476        }
1477
1478        pub mod option {
1479            use serde::Deserialize;
1480            use serde::de::Error as DeError;
1481
1482            use super::super as serde_string;
1483            use crate::OffsetDateTime;
1484            use time::format_description::well_known::Rfc3339;
1485
1486            pub fn serialize<S>(
1487                value: &Option<OffsetDateTime>,
1488                serializer: S,
1489            ) -> Result<S::Ok, S::Error>
1490            where
1491                S: serde::Serializer,
1492            {
1493                match value {
1494                    Some(value) => super::serialize(value, serializer),
1495                    None => serializer.serialize_none(),
1496                }
1497            }
1498
1499            pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<OffsetDateTime>, D::Error>
1500            where
1501                D: serde::Deserializer<'de>,
1502            {
1503                let value = <Option<String> as Deserialize>::deserialize(deserializer)?;
1504                value
1505                    .map(|value| OffsetDateTime::parse(&value, &Rfc3339).map_err(DeError::custom))
1506                    .transpose()
1507            }
1508
1509            pub fn deserialize_none_if<'de, D>(
1510                deserializer: D,
1511                none_if: &[&str],
1512            ) -> Result<Option<OffsetDateTime>, D::Error>
1513            where
1514                D: serde::Deserializer<'de>,
1515            {
1516                serde_string::deserialize_option_none_if_with(deserializer, none_if, |value| {
1517                    OffsetDateTime::parse(value, &Rfc3339)
1518                })
1519            }
1520        }
1521    }
1522
1523    pub mod as_unix_time {
1524        use serde::Deserialize;
1525        use serde::de::Error as DeError;
1526
1527        use crate::OffsetDateTime;
1528
1529        pub fn serialize<S>(value: &OffsetDateTime, serializer: S) -> Result<S::Ok, S::Error>
1530        where
1531            S: serde::Serializer,
1532        {
1533            serializer.serialize_str(&crate::format_unix_time(value))
1534        }
1535
1536        pub fn deserialize<'de, D>(deserializer: D) -> Result<OffsetDateTime, D::Error>
1537        where
1538            D: serde::Deserializer<'de>,
1539        {
1540            let value = <String as Deserialize>::deserialize(deserializer)?;
1541            let value = value.parse::<i64>().map_err(DeError::custom)?;
1542            OffsetDateTime::from_unix_timestamp(value).map_err(DeError::custom)
1543        }
1544
1545        pub fn serialize_none_if<S>(
1546            value: &Option<OffsetDateTime>,
1547            none_value: &str,
1548            serializer: S,
1549        ) -> Result<S::Ok, S::Error>
1550        where
1551            S: serde::Serializer,
1552        {
1553            super::serialize_option_or_sentinel(value.as_ref(), none_value, serializer, serialize)
1554        }
1555
1556        pub fn deserialize_none_if<'de, D>(
1557            deserializer: D,
1558            none_if: &[&str],
1559        ) -> Result<Option<OffsetDateTime>, D::Error>
1560        where
1561            D: serde::Deserializer<'de>,
1562        {
1563            super::deserialize_none_if_with(deserializer, none_if, |value| {
1564                let value = value.parse::<i64>().map_err(|error| error.to_string())?;
1565                OffsetDateTime::from_unix_timestamp(value).map_err(|error| error.to_string())
1566            })
1567        }
1568
1569        pub mod option {
1570            use serde::Deserialize;
1571            use serde::de::Error as DeError;
1572
1573            use super::super as serde_string;
1574            use crate::OffsetDateTime;
1575
1576            pub fn serialize<S>(
1577                value: &Option<OffsetDateTime>,
1578                serializer: S,
1579            ) -> Result<S::Ok, S::Error>
1580            where
1581                S: serde::Serializer,
1582            {
1583                match value {
1584                    Some(value) => super::serialize(value, serializer),
1585                    None => serializer.serialize_none(),
1586                }
1587            }
1588
1589            pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<OffsetDateTime>, D::Error>
1590            where
1591                D: serde::Deserializer<'de>,
1592            {
1593                let value = <Option<String> as Deserialize>::deserialize(deserializer)?;
1594                value
1595                    .map(|value| {
1596                        let value = value.parse::<i64>().map_err(DeError::custom)?;
1597                        OffsetDateTime::from_unix_timestamp(value).map_err(DeError::custom)
1598                    })
1599                    .transpose()
1600            }
1601
1602            pub fn deserialize_none_if<'de, D>(
1603                deserializer: D,
1604                none_if: &[&str],
1605            ) -> Result<Option<OffsetDateTime>, D::Error>
1606            where
1607                D: serde::Deserializer<'de>,
1608            {
1609                serde_string::deserialize_option_none_if_with(deserializer, none_if, |value| {
1610                    let value = value.parse::<i64>().map_err(|error| error.to_string())?;
1611                    OffsetDateTime::from_unix_timestamp(value).map_err(|error| error.to_string())
1612                })
1613            }
1614        }
1615    }
1616
1617    pub mod as_time {
1618        use serde::Deserialize;
1619        use serde::de::Error as DeError;
1620
1621        use crate::Time;
1622
1623        pub fn serialize<S>(value: &Time, serializer: S) -> Result<S::Ok, S::Error>
1624        where
1625            S: serde::Serializer,
1626        {
1627            serializer.serialize_str(&crate::format_time(value))
1628        }
1629
1630        pub fn deserialize<'de, D>(deserializer: D) -> Result<Time, D::Error>
1631        where
1632            D: serde::Deserializer<'de>,
1633        {
1634            let value = <String as Deserialize>::deserialize(deserializer)?;
1635            crate::parse_time(&value).map_err(DeError::custom)
1636        }
1637
1638        pub fn serialize_none_if<S>(
1639            value: &Option<Time>,
1640            none_value: &str,
1641            serializer: S,
1642        ) -> Result<S::Ok, S::Error>
1643        where
1644            S: serde::Serializer,
1645        {
1646            super::serialize_option_or_sentinel(value.as_ref(), none_value, serializer, serialize)
1647        }
1648
1649        pub fn deserialize_none_if<'de, D>(
1650            deserializer: D,
1651            none_if: &[&str],
1652        ) -> Result<Option<Time>, D::Error>
1653        where
1654            D: serde::Deserializer<'de>,
1655        {
1656            super::deserialize_none_if_with(deserializer, none_if, crate::parse_time)
1657        }
1658
1659        pub mod option {
1660            use serde::Deserialize;
1661            use serde::de::Error as DeError;
1662
1663            use crate::Time;
1664
1665            pub fn serialize<S>(value: &Option<Time>, serializer: S) -> Result<S::Ok, S::Error>
1666            where
1667                S: serde::Serializer,
1668            {
1669                match value {
1670                    Some(value) => super::serialize(value, serializer),
1671                    None => serializer.serialize_none(),
1672                }
1673            }
1674
1675            pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Time>, D::Error>
1676            where
1677                D: serde::Deserializer<'de>,
1678            {
1679                let value = <Option<String> as Deserialize>::deserialize(deserializer)?;
1680                let Some(value) = value else {
1681                    return Ok(None);
1682                };
1683                let value = value.trim();
1684                if value.is_empty() {
1685                    return Ok(None);
1686                }
1687                crate::parse_time(value).map(Some).map_err(DeError::custom)
1688            }
1689
1690            pub fn deserialize_none_if<'de, D>(
1691                deserializer: D,
1692                none_if: &[&str],
1693            ) -> Result<Option<Time>, D::Error>
1694            where
1695                D: serde::Deserializer<'de>,
1696            {
1697                let value = <Option<String> as Deserialize>::deserialize(deserializer)?;
1698                let Some(value) = value else {
1699                    return Ok(None);
1700                };
1701                if none_if.contains(&value.as_str()) {
1702                    return Ok(None);
1703                }
1704                let value = value.trim();
1705                if value.is_empty() {
1706                    return Ok(None);
1707                }
1708                crate::parse_time(value).map(Some).map_err(DeError::custom)
1709            }
1710        }
1711    }
1712
1713    fn serialize_display<T, S>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
1714    where
1715        T: fmt::Display,
1716        S: serde::Serializer,
1717    {
1718        serializer.serialize_str(&value.to_string())
1719    }
1720
1721    fn serialize_option_display<T, S>(value: Option<&T>, serializer: S) -> Result<S::Ok, S::Error>
1722    where
1723        T: fmt::Display,
1724        S: serde::Serializer,
1725    {
1726        match value {
1727            Some(value) => serialize_display(value, serializer),
1728            None => serializer.serialize_none(),
1729        }
1730    }
1731
1732    fn serialize_option_or_sentinel<T, S>(
1733        value: Option<&T>,
1734        none_value: &str,
1735        serializer: S,
1736        serialize: impl FnOnce(&T, S) -> Result<S::Ok, S::Error>,
1737    ) -> Result<S::Ok, S::Error>
1738    where
1739        S: serde::Serializer,
1740    {
1741        match value {
1742            Some(value) => serialize(value, serializer),
1743            None => serializer.serialize_str(none_value),
1744        }
1745    }
1746
1747    fn deserialize_from_str<'de, T, D>(deserializer: D) -> Result<T, D::Error>
1748    where
1749        T: FromStr,
1750        T::Err: fmt::Display,
1751        D: serde::Deserializer<'de>,
1752    {
1753        let value = String::deserialize(deserializer)?;
1754        value.parse::<T>().map_err(DeError::custom)
1755    }
1756
1757    fn deserialize_option_from_str<'de, T, D>(deserializer: D) -> Result<Option<T>, D::Error>
1758    where
1759        T: FromStr,
1760        T::Err: fmt::Display,
1761        D: serde::Deserializer<'de>,
1762    {
1763        let value = Option::<String>::deserialize(deserializer)?;
1764        value
1765            .map(|value| value.parse::<T>().map_err(DeError::custom))
1766            .transpose()
1767    }
1768
1769    fn deserialize_none_if_with<'de, T, D, E>(
1770        deserializer: D,
1771        none_if: &[&str],
1772        parse: impl FnOnce(&str) -> Result<T, E>,
1773    ) -> Result<Option<T>, D::Error>
1774    where
1775        D: serde::Deserializer<'de>,
1776        E: fmt::Display,
1777    {
1778        let value = String::deserialize(deserializer)?;
1779        parse_none_if_with(&value, none_if, parse)
1780    }
1781
1782    fn deserialize_option_none_if_with<'de, T, D, E>(
1783        deserializer: D,
1784        none_if: &[&str],
1785        parse: impl FnOnce(&str) -> Result<T, E>,
1786    ) -> Result<Option<T>, D::Error>
1787    where
1788        D: serde::Deserializer<'de>,
1789        E: fmt::Display,
1790    {
1791        let Some(value) = Option::<String>::deserialize(deserializer)? else {
1792            return Ok(None);
1793        };
1794        parse_none_if_with(&value, none_if, parse)
1795    }
1796
1797    fn parse_none_if_with<T, E, D>(
1798        value: &str,
1799        none_if: &[&str],
1800        parse: impl FnOnce(&str) -> Result<T, E>,
1801    ) -> Result<Option<T>, D>
1802    where
1803        E: fmt::Display,
1804        D: DeError,
1805    {
1806        if none_if.contains(&value) {
1807            return Ok(None);
1808        }
1809        parse(value).map(Some).map_err(DeError::custom)
1810    }
1811
1812    fn deserialize_bool(value: &str) -> Result<bool, &'static str> {
1813        match value {
1814            "1" => Ok(true),
1815            "0" => Ok(false),
1816            value if value.eq_ignore_ascii_case("true") => Ok(true),
1817            value if value.eq_ignore_ascii_case("false") => Ok(false),
1818            _ => Err("invalid boolean string"),
1819        }
1820    }
1821}
1822
1823#[cfg(feature = "serde")]
1824#[allow(clippy::missing_errors_doc)]
1825pub mod serde_integer {
1826    pub mod as_unix_time {
1827        use serde::Deserialize;
1828        use serde::de::Error as DeError;
1829
1830        use crate::OffsetDateTime;
1831
1832        pub fn serialize<S>(value: &OffsetDateTime, serializer: S) -> Result<S::Ok, S::Error>
1833        where
1834            S: serde::Serializer,
1835        {
1836            serializer.serialize_i64(value.unix_timestamp())
1837        }
1838
1839        pub fn deserialize<'de, D>(deserializer: D) -> Result<OffsetDateTime, D::Error>
1840        where
1841            D: serde::Deserializer<'de>,
1842        {
1843            let value = <i64 as Deserialize>::deserialize(deserializer)?;
1844            OffsetDateTime::from_unix_timestamp(value).map_err(DeError::custom)
1845        }
1846
1847        pub mod option {
1848            use serde::Deserialize;
1849            use serde::de::Error as DeError;
1850
1851            use crate::OffsetDateTime;
1852
1853            pub fn serialize<S>(
1854                value: &Option<OffsetDateTime>,
1855                serializer: S,
1856            ) -> Result<S::Ok, S::Error>
1857            where
1858                S: serde::Serializer,
1859            {
1860                match value {
1861                    Some(value) => super::serialize(value, serializer),
1862                    None => serializer.serialize_none(),
1863                }
1864            }
1865
1866            pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<OffsetDateTime>, D::Error>
1867            where
1868                D: serde::Deserializer<'de>,
1869            {
1870                let value = <Option<i64> as Deserialize>::deserialize(deserializer)?;
1871                value
1872                    .map(|value| {
1873                        OffsetDateTime::from_unix_timestamp(value).map_err(DeError::custom)
1874                    })
1875                    .transpose()
1876            }
1877        }
1878    }
1879
1880    pub mod as_bool {
1881        use crate::serde_string::as_bool as string_bool;
1882
1883        pub fn serialize<S>(value: &bool, serializer: S) -> Result<S::Ok, S::Error>
1884        where
1885            S: serde::Serializer,
1886        {
1887            serializer.serialize_u8(u8::from(*value))
1888        }
1889
1890        pub fn deserialize<'de, D>(deserializer: D) -> Result<bool, D::Error>
1891        where
1892            D: serde::Deserializer<'de>,
1893        {
1894            string_bool::deserialize(deserializer)
1895        }
1896
1897        pub mod option {
1898            use crate::serde_string::as_bool::option as string_bool_option;
1899
1900            pub fn serialize<S>(value: &Option<bool>, serializer: S) -> Result<S::Ok, S::Error>
1901            where
1902                S: serde::Serializer,
1903            {
1904                match value {
1905                    Some(value) => super::serialize(value, serializer),
1906                    None => serializer.serialize_none(),
1907                }
1908            }
1909
1910            pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<bool>, D::Error>
1911            where
1912                D: serde::Deserializer<'de>,
1913            {
1914                string_bool_option::deserialize(deserializer)
1915            }
1916        }
1917    }
1918}
1919
1920#[cfg(feature = "json")]
1921pub mod treat_error_as_none {
1922    use serde::de::DeserializeOwned;
1923    use serde::{Deserialize, Deserializer, Serialize, Serializer};
1924
1925    /// Serializes an optional value, omitting `None`.
1926    ///
1927    /// # Errors
1928    ///
1929    /// Returns an error if serializing the inner value fails.
1930    pub fn serialize<S, T>(value: &Option<T>, serializer: S) -> Result<S::Ok, S::Error>
1931    where
1932        S: Serializer,
1933        T: Serialize,
1934    {
1935        match value {
1936            Some(inner) => inner.serialize(serializer),
1937            None => serializer.serialize_none(),
1938        }
1939    }
1940
1941    /// Deserializes an optional value, treating invalid inner values as `None`.
1942    ///
1943    /// # Errors
1944    ///
1945    /// Returns an error if the input cannot be read as JSON.
1946    pub fn deserialize<'de, D, T>(deserializer: D) -> Result<Option<T>, D::Error>
1947    where
1948        D: Deserializer<'de>,
1949        T: DeserializeOwned,
1950    {
1951        let value = serde_json::Value::deserialize(deserializer)?;
1952        match T::deserialize(value) {
1953            Ok(parsed) => Ok(Some(parsed)),
1954            Err(_) => Ok(None),
1955        }
1956    }
1957}
1958
1959/// Inserts a header into a header map.
1960///
1961/// # Errors
1962///
1963/// Returns an error if the header name or value is invalid.
1964pub fn insert_header(
1965    headers: &mut http::HeaderMap,
1966    name: &'static str,
1967    value: &str,
1968) -> Result<(), Error> {
1969    headers.insert(
1970        HeaderName::from_bytes(name.as_bytes())?,
1971        HeaderValue::from_str(value)?,
1972    );
1973    Ok(())
1974}
1975
1976#[must_use]
1977pub fn has_json_content_type(headers: &http::HeaderMap) -> bool {
1978    headers
1979        .get(CONTENT_TYPE)
1980        .and_then(|value| value.to_str().ok())
1981        .is_some_and(is_json_media_type)
1982}
1983
1984fn is_json_media_type(value: &str) -> bool {
1985    let media_type = value.split(';').next().unwrap_or(value).trim();
1986    if media_type.eq_ignore_ascii_case("application/json") {
1987        return true;
1988    }
1989
1990    let Some((_, subtype)) = media_type.rsplit_once('/') else {
1991        return false;
1992    };
1993    ends_with_ignore_ascii_case(subtype, "+json")
1994}
1995
1996fn ends_with_ignore_ascii_case(value: &str, suffix: &str) -> bool {
1997    let value = value.as_bytes();
1998    let suffix = suffix.as_bytes();
1999    value.len() >= suffix.len() && value[value.len() - suffix.len()..].eq_ignore_ascii_case(suffix)
2000}
2001
2002fn append_percent_encoded(out: &mut String, bytes: &[u8]) {
2003    const HEX: &[u8; 16] = b"0123456789ABCDEF";
2004
2005    for &byte in bytes {
2006        if is_unreserved(byte) {
2007            out.push(byte as char);
2008        } else {
2009            out.push('%');
2010            out.push(HEX[(byte >> 4) as usize] as char);
2011            out.push(HEX[(byte & 0x0f) as usize] as char);
2012        }
2013    }
2014}
2015
2016const fn is_unreserved(byte: u8) -> bool {
2017    matches!(
2018        byte,
2019        b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'.' | b'_' | b'~'
2020    )
2021}
2022
2023#[cfg(test)]
2024mod tests {
2025    use super::*;
2026
2027    #[cfg(all(feature = "serde", feature = "json"))]
2028    mod required_f64_none_if {
2029        use crate::serde_string::as_f64;
2030
2031        #[allow(clippy::ref_option)]
2032        pub fn serialize<S>(value: &Option<f64>, serializer: S) -> Result<S::Ok, S::Error>
2033        where
2034            S: serde::Serializer,
2035        {
2036            as_f64::serialize_none_if(value, "NA", serializer)
2037        }
2038
2039        pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<f64>, D::Error>
2040        where
2041            D: serde::Deserializer<'de>,
2042        {
2043            as_f64::deserialize_none_if(deserializer, &["NA", "-"])
2044        }
2045    }
2046
2047    #[cfg(all(feature = "serde", feature = "json"))]
2048    mod optional_f64_none_if {
2049        use crate::serde_string::as_f64::{self, option};
2050
2051        #[allow(clippy::ref_option)]
2052        pub fn serialize<S>(value: &Option<f64>, serializer: S) -> Result<S::Ok, S::Error>
2053        where
2054            S: serde::Serializer,
2055        {
2056            as_f64::serialize_none_if(value, "NA", serializer)
2057        }
2058
2059        pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<f64>, D::Error>
2060        where
2061            D: serde::Deserializer<'de>,
2062        {
2063            option::deserialize_none_if(deserializer, &["NA", "-"])
2064        }
2065    }
2066
2067    #[test]
2068    fn encodes_path_segments() {
2069        let mut out = String::new();
2070        append_path_segment(&mut out, "a/b c");
2071        assert_eq!(out, "a%2Fb%20c");
2072    }
2073
2074    #[test]
2075    fn appends_query_pairs() {
2076        let mut out = String::from("/pets");
2077        let mut first = true;
2078        append_query_pair(&mut out, &mut first, "tag name", "small/dog");
2079        append_query_pair(&mut out, &mut first, "limit", "10");
2080        assert_eq!(out, "/pets?tag%20name=small%2Fdog&limit=10");
2081    }
2082
2083    #[test]
2084    fn parses_range_strings() {
2085        assert_eq!(parse_range::<u8>("14-17").unwrap(), (Some(14), Some(17)));
2086        assert_eq!(parse_range::<u8>("14-").unwrap(), (Some(14), None));
2087        assert_eq!(parse_range::<u8>("-17").unwrap(), (None, Some(17)));
2088        assert_eq!(parse_range::<u8>("").unwrap(), (None, None));
2089        assert!(matches!(
2090            parse_range::<u8>("14-17-20"),
2091            Err(ParseRangeError::TooManySeparators)
2092        ));
2093    }
2094
2095    #[test]
2096    fn formats_range_strings() {
2097        assert_eq!(format_range(&Some(14), &Some(17)), "14-17");
2098        assert_eq!(format_range(&Some(14), &None::<u8>), "14-");
2099        assert_eq!(format_range(&None::<u8>, &Some(17)), "-17");
2100        assert_eq!(format_range(&None::<u8>, &None::<u8>), "");
2101    }
2102
2103    #[test]
2104    fn parses_and_formats_time_strings() {
2105        let time = parse_time("0620").unwrap();
2106        assert_eq!(time.hour(), 6);
2107        assert_eq!(time.minute(), 20);
2108        assert_eq!(format_time(&time), "0620");
2109        assert_eq!(parse_time("6:20"), Err(ParseTimeError::InvalidFormat));
2110        assert_eq!(parse_time("2400"), Err(ParseTimeError::ComponentRange));
2111    }
2112
2113    #[test]
2114    fn parses_and_formats_date_strings() {
2115        let date = parse_date("2024-07-16").unwrap();
2116        assert_eq!(
2117            date,
2118            Date::from_calendar_date(2024, Month::July, 16).unwrap()
2119        );
2120        assert_eq!(format_date(&date), "2024-07-16");
2121        assert_eq!(parse_date("2024-7-16"), Err(ParseDateError::InvalidFormat));
2122        assert_eq!(parse_date("not-a-date"), Err(ParseDateError::InvalidFormat));
2123    }
2124
2125    #[test]
2126    fn parses_and_formats_naive_datetime_strings() {
2127        let datetime = parse_naive_datetime("2024-07-16T23:59:00").unwrap();
2128        assert_eq!(datetime.hour(), 23);
2129        assert_eq!(datetime.minute(), 59);
2130        assert_eq!(datetime.second(), 0);
2131        assert_eq!(format_naive_datetime(&datetime), "2024-07-16T23:59:00");
2132        assert_eq!(
2133            parse_naive_datetime("2024-07-16T23:59"),
2134            Err(ParseNaiveDateTimeError::InvalidFormat)
2135        );
2136        assert_eq!(
2137            parse_naive_datetime("2024-07-16 23:59:00"),
2138            Err(ParseNaiveDateTimeError::InvalidFormat)
2139        );
2140    }
2141
2142    #[test]
2143    fn formats_unix_time_seconds() {
2144        let datetime = OffsetDateTime::from_unix_timestamp(-1).unwrap();
2145        assert_eq!(format_unix_time(&datetime), "-1");
2146    }
2147
2148    #[cfg(all(feature = "serde", feature = "json"))]
2149    #[test]
2150    fn serde_string_naive_datetime_round_trips() {
2151        #[derive(serde::Deserialize, serde::Serialize)]
2152        struct Value {
2153            #[serde(with = "crate::serde_string::as_naive_datetime")]
2154            at: PrimitiveDateTime,
2155        }
2156
2157        let at = parse_naive_datetime("2024-07-16T23:59:00").unwrap();
2158        let encoded = serde_json::to_value(Value { at }).unwrap();
2159        assert_eq!(encoded, serde_json::json!({ "at": "2024-07-16T23:59:00" }));
2160
2161        let decoded = serde_json::from_value::<Value>(encoded).unwrap();
2162        assert_eq!(decoded.at, at);
2163    }
2164
2165    #[cfg(all(feature = "serde", feature = "json"))]
2166    #[test]
2167    fn serde_string_date_round_trips() {
2168        #[derive(serde::Deserialize, serde::Serialize)]
2169        struct Value {
2170            #[serde(with = "crate::serde_string::as_date")]
2171            day: Date,
2172        }
2173
2174        let date = Date::from_calendar_date(2024, Month::July, 16).unwrap();
2175        let encoded = serde_json::to_value(Value { day: date }).unwrap();
2176        assert_eq!(encoded, serde_json::json!({ "day": "2024-07-16" }));
2177
2178        let decoded = serde_json::from_value::<Value>(encoded).unwrap();
2179        assert_eq!(decoded.day, date);
2180    }
2181
2182    #[test]
2183    fn recognizes_json_content_types() {
2184        let mut headers = http::HeaderMap::new();
2185        headers.insert(
2186            CONTENT_TYPE,
2187            http::HeaderValue::from_static("application/problem+json; charset=utf-8"),
2188        );
2189        assert!(has_json_content_type(&headers));
2190    }
2191
2192    #[test]
2193    fn response_parts_holds_status_headers_body() {
2194        let mut headers = http::HeaderMap::new();
2195        headers.insert(
2196            CONTENT_TYPE,
2197            http::HeaderValue::from_static("application/json"),
2198        );
2199        let body = br#"{"ok":true}"#.to_vec();
2200        let parts = ResponseParts {
2201            status: http::StatusCode::OK,
2202            headers,
2203            body,
2204        };
2205        assert_eq!(parts.status, http::StatusCode::OK);
2206        assert_eq!(parts.headers.get(CONTENT_TYPE).unwrap(), "application/json");
2207        assert_eq!(parts.body, br#"{"ok":true}"#);
2208    }
2209
2210    #[cfg(feature = "json")]
2211    #[test]
2212    fn projected_json_unwraps_and_maps_fields() {
2213        let body = br#"{
2214            "odata.metadata": "https://example.test/metadata",
2215            "value": [
2216                {"Link": "https://example.test/a", "Name": "A"},
2217                {"Link": "https://example.test/b", "Name": "B"}
2218            ]
2219        }"#;
2220
2221        let rows = from_projected_json_slice::<Vec<JsonValue>>(body, "value", None).unwrap();
2222        assert_eq!(rows.len(), 2);
2223        assert_eq!(rows[0]["Name"], "A");
2224
2225        let links = from_projected_json_slice::<Vec<String>>(body, "value", Some("Link")).unwrap();
2226        assert_eq!(
2227            links,
2228            vec![
2229                "https://example.test/a".to_owned(),
2230                "https://example.test/b".to_owned()
2231            ]
2232        );
2233    }
2234
2235    #[cfg(feature = "json")]
2236    #[test]
2237    fn projected_json_preserves_optional_missing_fields_as_null() {
2238        let missing = br#"{"metadata":"present"}"#;
2239        let value = from_projected_json_slice::<Option<Vec<String>>>(missing, "value", None)
2240            .expect("optional missing projection");
2241        assert_eq!(value, None);
2242
2243        let rows = br#"{"value":[{}, {"Link":"present"}]}"#;
2244        let links = from_projected_json_slice::<Vec<Option<String>>>(rows, "value", Some("Link"))
2245            .expect("optional mapped field");
2246        assert_eq!(links, vec![None, Some("present".to_owned())]);
2247    }
2248
2249    #[cfg(feature = "json")]
2250    #[test]
2251    fn projected_json_rejects_invalid_container_shapes() {
2252        let scalar = from_projected_json_slice::<Vec<String>>(
2253            br#"{"value":"not-an-array"}"#,
2254            "value",
2255            Some("Link"),
2256        );
2257        assert!(matches!(scalar, Err(Error::InvalidResponse(_))));
2258
2259        let scalar_item = from_projected_json_slice::<Vec<String>>(
2260            br#"{"value":["not-an-object"]}"#,
2261            "value",
2262            Some("Link"),
2263        );
2264        assert!(matches!(scalar_item, Err(Error::InvalidResponse(_))));
2265    }
2266
2267    #[cfg(all(feature = "serde", feature = "json"))]
2268    #[test]
2269    fn serde_string_bool_accepts_string_and_numeric_values() {
2270        #[derive(serde::Deserialize, serde::Serialize)]
2271        struct Value {
2272            #[serde(with = "crate::serde_string::as_bool")]
2273            monitored: bool,
2274        }
2275
2276        let numeric = serde_json::from_str::<Value>(r#"{"monitored":0}"#).unwrap();
2277        assert!(!numeric.monitored);
2278
2279        let string = serde_json::from_str::<Value>(r#"{"monitored":"1"}"#).unwrap();
2280        assert!(string.monitored);
2281
2282        let encoded = serde_json::to_value(Value { monitored: false }).unwrap();
2283        assert_eq!(encoded, serde_json::json!({ "monitored": "0" }));
2284    }
2285
2286    #[cfg(all(feature = "serde", feature = "json"))]
2287    #[test]
2288    fn serde_string_none_if_is_strict_and_canonical() {
2289        #[derive(Debug, serde::Deserialize, serde::Serialize)]
2290        struct Value {
2291            #[serde(with = "required_f64_none_if")]
2292            required: Option<f64>,
2293            #[serde(default, with = "optional_f64_none_if")]
2294            optional: Option<f64>,
2295        }
2296
2297        let valid =
2298            serde_json::from_str::<Value>(r#"{"required":"28.7","optional":"10.5"}"#).unwrap();
2299        assert_eq!(valid.required, Some(28.7));
2300        assert_eq!(valid.optional, Some(10.5));
2301
2302        let sentinel =
2303            serde_json::from_str::<Value>(r#"{"required":"-","optional":"NA"}"#).unwrap();
2304        assert_eq!(sentinel.required, None);
2305        assert_eq!(sentinel.optional, None);
2306
2307        let null_optional =
2308            serde_json::from_str::<Value>(r#"{"required":"28.7","optional":null}"#).unwrap();
2309        assert_eq!(null_optional.optional, None);
2310        assert!(serde_json::from_str::<Value>(r#"{"required":null}"#).is_err());
2311        assert!(serde_json::from_str::<Value>(r#"{"required":"unexpected"}"#).is_err());
2312
2313        let encoded = serde_json::to_value(Value {
2314            required: None,
2315            optional: Some(10.5),
2316        })
2317        .unwrap();
2318        assert_eq!(
2319            encoded,
2320            serde_json::json!({"required": "NA", "optional": "10.5"})
2321        );
2322    }
2323
2324    #[cfg(all(feature = "serde", feature = "json"))]
2325    #[test]
2326    fn serde_string_none_if_preserves_bool_and_time_parsers() {
2327        use crate::serde_string::{as_bool, as_time::option as time_option};
2328        use serde_json::Value;
2329
2330        let bool_sentinel =
2331            as_bool::deserialize_none_if(Value::String("NA".to_owned()), &["NA"]).unwrap();
2332        assert_eq!(bool_sentinel, None);
2333
2334        let bool_numeric = as_bool::deserialize_none_if(Value::from(1), &["NA"]).unwrap();
2335        assert_eq!(bool_numeric, Some(true));
2336
2337        let empty_time =
2338            time_option::deserialize_none_if(Value::String("  ".to_owned()), &["-"]).unwrap();
2339        assert_eq!(empty_time, None);
2340        assert!(
2341            time_option::deserialize_none_if(Value::String("invalid".to_owned()), &["-"]).is_err()
2342        );
2343    }
2344
2345    #[cfg(all(feature = "serde", feature = "json"))]
2346    #[test]
2347    fn serde_integer_bool_accepts_numeric_values() {
2348        #[derive(serde::Deserialize, serde::Serialize)]
2349        struct Value {
2350            #[serde(with = "crate::serde_integer::as_bool")]
2351            monitored: bool,
2352        }
2353
2354        let numeric = serde_json::from_str::<Value>(r#"{"monitored":0}"#).unwrap();
2355        assert!(!numeric.monitored);
2356
2357        let encoded = serde_json::to_value(Value { monitored: true }).unwrap();
2358        assert_eq!(encoded, serde_json::json!({ "monitored": 1 }));
2359    }
2360
2361    #[cfg(all(feature = "serde", feature = "json"))]
2362    #[test]
2363    fn serde_string_unix_time_round_trips() {
2364        #[derive(serde::Deserialize, serde::Serialize)]
2365        struct Value {
2366            #[serde(with = "crate::serde_string::as_unix_time")]
2367            at: OffsetDateTime,
2368        }
2369
2370        let at = OffsetDateTime::from_unix_timestamp(1_719_892_800).unwrap();
2371        let encoded = serde_json::to_value(Value { at }).unwrap();
2372        assert_eq!(encoded, serde_json::json!({ "at": "1719892800" }));
2373
2374        let decoded = serde_json::from_value::<Value>(encoded).unwrap();
2375        assert_eq!(decoded.at, at);
2376    }
2377
2378    #[cfg(all(feature = "serde", feature = "json"))]
2379    #[test]
2380    fn serde_integer_unix_time_round_trips_and_handles_null() {
2381        #[derive(serde::Deserialize, serde::Serialize)]
2382        struct Value {
2383            #[serde(with = "crate::serde_integer::as_unix_time")]
2384            at: OffsetDateTime,
2385            #[serde(with = "crate::serde_integer::as_unix_time::option")]
2386            maybe_at: Option<OffsetDateTime>,
2387        }
2388
2389        let at = OffsetDateTime::from_unix_timestamp(1_719_892_800).unwrap();
2390        let encoded = serde_json::to_value(Value { at, maybe_at: None }).unwrap();
2391        assert_eq!(
2392            encoded,
2393            serde_json::json!({ "at": 1_719_892_800, "maybe_at": null })
2394        );
2395
2396        let decoded = serde_json::from_value::<Value>(encoded).unwrap();
2397        assert_eq!(decoded.at, at);
2398        assert_eq!(decoded.maybe_at, None);
2399    }
2400
2401    #[cfg(all(feature = "serde", feature = "json"))]
2402    #[test]
2403    fn serde_integer_unix_time_rejects_out_of_range_values() {
2404        #[derive(serde::Deserialize)]
2405        #[allow(dead_code)]
2406        struct Value {
2407            #[serde(with = "crate::serde_integer::as_unix_time")]
2408            at: OffsetDateTime,
2409        }
2410
2411        assert!(serde_json::from_str::<Value>(r#"{"at":9223372036854775807}"#).is_err());
2412    }
2413}