Skip to main content

polyoxide_core/
lib.rs

1//! # polyoxide-core
2//!
3//! Core utilities and shared types for Polyoxide Polymarket API clients.
4//!
5//! This crate provides common functionality used across `polyoxide-clob`, `polyoxide-gamma`, `polyoxide-data`, and `polyoxide-relay`:
6//! - Shared error types and error handling
7//! - HTTP client configuration
8//! - Request builder utilities
9//! - HMAC API-credential signing ([`Signer`])
10//! - Per-endpoint rate limiting with retry/backoff ([`RateLimiter`])
11//! - Optional OS keychain credential storage (behind the `keychain` feature)
12//!
13//! ## HTTP Client
14//!
15//! Use [`HttpClientBuilder`] to create configured HTTP clients:
16//!
17//! ```
18//! use polyoxide_core::HttpClientBuilder;
19//!
20//! let client = HttpClientBuilder::new("https://api.example.com")
21//!     .timeout_ms(60_000)
22//!     .build()
23//!     .unwrap();
24//! ```
25//!
26//! ## Error Handling
27//!
28//! Use the [`impl_api_error_conversions`] macro to reduce boilerplate in error types.
29
30// Compile the crate README's `rust` code fences as doctests so broken examples
31// fail CI. `#[cfg(doctest)]` keeps this out of normal builds and `cargo doc`.
32#[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;
44pub mod session_signer;
45pub mod signer_limit;
46
47#[cfg(feature = "keychain")]
48pub mod keychain;
49
50/// Maximum number of characters to include in log messages containing response bodies.
51const LOG_BODY_MAX_LEN: usize = 512;
52
53/// Truncate a string for safe inclusion in log output.
54///
55/// Returns the original string if it fits within `LOG_BODY_MAX_LEN`,
56/// otherwise truncates at a UTF-8 boundary and appends `... [truncated]`.
57pub fn truncate_for_log(s: &str) -> std::borrow::Cow<'_, str> {
58    if s.len() <= LOG_BODY_MAX_LEN {
59        std::borrow::Cow::Borrowed(s)
60    } else {
61        let truncated = &s[..s.floor_char_boundary(LOG_BODY_MAX_LEN)];
62        std::borrow::Cow::Owned(format!("{}... [truncated]", truncated))
63    }
64}
65
66pub use auth::{current_timestamp, Base64Format, Signer};
67pub use client::{
68    retry_after_header, HttpClient, HttpClientBuilder, DEFAULT_POOL_SIZE, DEFAULT_TIMEOUT_MS,
69};
70pub use error::ApiError;
71pub use rate_limit::{RateLimiter, RetryConfig};
72pub use request::{QueryBuilder, Request, RequestError};
73pub use session_signer::{DepositWalletRole, SessionSignerScope};
74pub use signer_limit::{
75    BurstCapacityExceeded, RateLimitStatus, SignerLimiter, Tier, TradingBucket, TradingRequest,
76};
77
78#[cfg(feature = "keychain")]
79pub use keychain::KeychainError;
80
81#[cfg(test)]
82mod tests {
83    use super::*;
84
85    #[test]
86    fn test_truncate_for_log_short_string_unchanged() {
87        let short = "hello world";
88        let result = truncate_for_log(short);
89        assert_eq!(result.as_ref(), short);
90    }
91
92    #[test]
93    fn test_truncate_for_log_exact_limit_unchanged() {
94        let exact = "a".repeat(LOG_BODY_MAX_LEN);
95        let result = truncate_for_log(&exact);
96        assert_eq!(result.as_ref(), exact.as_str());
97    }
98
99    #[test]
100    fn test_truncate_for_log_over_limit_truncated() {
101        let long = "x".repeat(LOG_BODY_MAX_LEN + 100);
102        let result = truncate_for_log(&long);
103        assert!(result.ends_with("... [truncated]"));
104        assert!(result.len() < long.len());
105    }
106
107    #[test]
108    fn test_truncate_for_log_multibyte_char_boundary() {
109        // Create a string where the 512th byte falls inside a multi-byte char
110        let mut s = "a".repeat(LOG_BODY_MAX_LEN - 1);
111        s.push('\u{1F600}'); // 4-byte emoji at position 511-514
112        s.push_str("overflow");
113        let result = truncate_for_log(&s);
114        assert!(result.ends_with("... [truncated]"));
115        // Should not panic or produce invalid UTF-8
116        assert!(result.is_char_boundary(0));
117    }
118}