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