1#[cfg(doctest)]
33#[doc = include_str!("../README.md")]
34struct ReadmeDoctests;
35
36#[macro_use]
37pub mod macros;
38
39pub mod auth;
40pub mod client;
41pub mod error;
42pub mod rate_limit;
43pub mod request;
44
45#[cfg(feature = "keychain")]
46pub mod keychain;
47
48const LOG_BODY_MAX_LEN: usize = 512;
50
51pub fn truncate_for_log(s: &str) -> std::borrow::Cow<'_, str> {
56 if s.len() <= LOG_BODY_MAX_LEN {
57 std::borrow::Cow::Borrowed(s)
58 } else {
59 let truncated = &s[..s.floor_char_boundary(LOG_BODY_MAX_LEN)];
60 std::borrow::Cow::Owned(format!("{}... [truncated]", truncated))
61 }
62}
63
64pub use auth::{current_timestamp, Base64Format, Signer};
65pub use client::{
66 retry_after_header, HttpClient, HttpClientBuilder, DEFAULT_POOL_SIZE, DEFAULT_TIMEOUT_MS,
67};
68pub use error::ApiError;
69pub use rate_limit::{RateLimiter, RetryConfig};
70pub use request::{QueryBuilder, Request, RequestError};
71
72#[cfg(feature = "keychain")]
73pub use keychain::KeychainError;
74
75#[cfg(test)]
76mod tests {
77 use super::*;
78
79 #[test]
80 fn test_truncate_for_log_short_string_unchanged() {
81 let short = "hello world";
82 let result = truncate_for_log(short);
83 assert_eq!(result.as_ref(), short);
84 }
85
86 #[test]
87 fn test_truncate_for_log_exact_limit_unchanged() {
88 let exact = "a".repeat(LOG_BODY_MAX_LEN);
89 let result = truncate_for_log(&exact);
90 assert_eq!(result.as_ref(), exact.as_str());
91 }
92
93 #[test]
94 fn test_truncate_for_log_over_limit_truncated() {
95 let long = "x".repeat(LOG_BODY_MAX_LEN + 100);
96 let result = truncate_for_log(&long);
97 assert!(result.ends_with("... [truncated]"));
98 assert!(result.len() < long.len());
99 }
100
101 #[test]
102 fn test_truncate_for_log_multibyte_char_boundary() {
103 let mut s = "a".repeat(LOG_BODY_MAX_LEN - 1);
105 s.push('\u{1F600}'); s.push_str("overflow");
107 let result = truncate_for_log(&s);
108 assert!(result.ends_with("... [truncated]"));
109 assert!(result.is_char_boundary(0));
111 }
112}