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