#[cfg(feature = "grpc")]
pub mod grpc;
pub mod jsonrpc;
pub mod rest;
#[cfg(feature = "websocket")]
pub mod websocket;
#[cfg(feature = "grpc")]
pub use grpc::GrpcTransport;
pub use jsonrpc::JsonRpcTransport;
pub use rest::RestTransport;
#[cfg(feature = "websocket")]
pub use websocket::WebSocketTransport;
const MAX_ERROR_BODY_LEN: usize = 512;
pub(crate) fn truncate_body(body: &str) -> String {
if body.len() <= MAX_ERROR_BODY_LEN {
body.to_owned()
} else {
let mut end = MAX_ERROR_BODY_LEN;
while end > 0 && !body.is_char_boundary(end) {
end -= 1;
}
format!("{}...(truncated)", &body[..end])
}
}
use std::collections::HashMap;
use std::future::Future;
use std::pin::Pin;
use crate::error::ClientResult;
use crate::streaming::EventStream;
pub trait Transport: Send + Sync + 'static {
fn send_request<'a>(
&'a self,
method: &'a str,
params: serde_json::Value,
extra_headers: &'a HashMap<String, String>,
) -> Pin<Box<dyn Future<Output = ClientResult<serde_json::Value>> + Send + 'a>>;
fn send_streaming_request<'a>(
&'a self,
method: &'a str,
params: serde_json::Value,
extra_headers: &'a HashMap<String, String>,
) -> Pin<Box<dyn Future<Output = ClientResult<EventStream>> + Send + 'a>>;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn truncate_body_short_string_unchanged() {
let short = "hello world";
let result = truncate_body(short);
assert_eq!(result, short);
}
#[test]
fn truncate_body_exact_limit_unchanged() {
let body = "x".repeat(MAX_ERROR_BODY_LEN);
let result = truncate_body(&body);
assert_eq!(result, body, "body at exact limit should not be truncated");
}
#[test]
fn truncate_body_over_limit_is_truncated() {
let body = "a".repeat(MAX_ERROR_BODY_LEN + 100);
let result = truncate_body(&body);
assert!(
result.len() < body.len(),
"result should be shorter than input"
);
assert!(
result.ends_with("...(truncated)"),
"truncated body should end with marker: {result}"
);
assert!(
result.starts_with(&"a".repeat(MAX_ERROR_BODY_LEN)),
"truncated body should start with the first MAX_ERROR_BODY_LEN chars"
);
}
#[test]
fn truncate_body_empty_string() {
let result = truncate_body("");
assert_eq!(result, "");
}
#[test]
fn truncate_body_multibyte_utf8_no_panic() {
let base = "é".repeat(MAX_ERROR_BODY_LEN); assert!(base.len() > MAX_ERROR_BODY_LEN);
let result = truncate_body(&base);
assert!(
result.ends_with("...(truncated)"),
"should be truncated: {result}"
);
let prefix = result.trim_end_matches("...(truncated)");
assert!(
prefix.len() <= MAX_ERROR_BODY_LEN,
"prefix should not exceed limit"
);
}
#[test]
fn truncate_body_mid_multibyte_boundary() {
let mut body = "a".repeat(MAX_ERROR_BODY_LEN - 1); body.push('€'); assert_eq!(body.len(), MAX_ERROR_BODY_LEN + 2);
assert!(
!body.is_char_boundary(MAX_ERROR_BODY_LEN),
"byte 512 should be mid-character"
);
let result = truncate_body(&body);
assert!(
result.ends_with("...(truncated)"),
"should be truncated: {result}"
);
let prefix = result.trim_end_matches("...(truncated)");
assert_eq!(
prefix.len(),
MAX_ERROR_BODY_LEN - 1,
"should truncate to last valid char boundary before limit"
);
assert_eq!(prefix, "a".repeat(MAX_ERROR_BODY_LEN - 1));
}
#[test]
fn truncate_body_two_byte_char_at_boundary() {
let mut body = "b".repeat(MAX_ERROR_BODY_LEN - 1); body.push('é'); assert_eq!(body.len(), MAX_ERROR_BODY_LEN + 1);
assert!(
!body.is_char_boundary(MAX_ERROR_BODY_LEN),
"byte 512 should be inside 'é'"
);
let result = truncate_body(&body);
let prefix = result.trim_end_matches("...(truncated)");
assert_eq!(prefix.len(), MAX_ERROR_BODY_LEN - 1);
}
}