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