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