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 fn request(self) -> Result<http::Request<Vec<u8>>, Error>;
96
97 fn decode<B: AsRef<[u8]>>(response: ResponseParts<B>) -> Result<Self::Response, Error>;
103}
104
105#[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#[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#[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#[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#[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#[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
326pub 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
387pub 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
425pub 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
452pub 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 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 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
1664pub 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}