pub mod engage;
pub mod media;
pub mod profile_inference;
pub mod read;
pub mod write;
#[cfg(test)]
mod e2e_tests;
use crate::error::XApiError;
pub const MAX_TWEET_LENGTH: usize = 280;
#[derive(Debug, thiserror::Error)]
pub enum ToolkitError {
#[error(transparent)]
XApi(#[from] XApiError),
#[error("invalid input: {message}")]
InvalidInput { message: String },
#[error("tweet too long: {length} characters (max {max})")]
TweetTooLong { length: usize, max: usize },
#[error("unsupported media type for file: {path}")]
UnsupportedMediaType { path: String },
#[error("media too large: {size} bytes (max {max} for {media_type})")]
MediaTooLarge {
size: u64,
max: u64,
media_type: String,
},
#[error("thread failed at tweet {failed_index}: posted {posted}/{total} tweets")]
ThreadPartialFailure {
posted_ids: Vec<String>,
failed_index: usize,
posted: usize,
total: usize,
#[source]
source: Box<XApiError>,
},
}
pub fn validate_tweet_length(text: &str) -> Result<(), ToolkitError> {
if text.len() > MAX_TWEET_LENGTH {
return Err(ToolkitError::TweetTooLong {
length: text.len(),
max: MAX_TWEET_LENGTH,
});
}
Ok(())
}
fn validate_id(id: &str, name: &str) -> Result<(), ToolkitError> {
if id.is_empty() {
return Err(ToolkitError::InvalidInput {
message: format!("{name} must not be empty"),
});
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn validate_tweet_length_ok() {
assert!(validate_tweet_length("Hello world").is_ok());
}
#[test]
fn validate_tweet_length_exactly_280() {
let text = "a".repeat(280);
assert!(validate_tweet_length(&text).is_ok());
}
#[test]
fn validate_tweet_length_too_long() {
let text = "a".repeat(281);
let err = validate_tweet_length(&text).unwrap_err();
assert!(matches!(
err,
ToolkitError::TweetTooLong {
length: 281,
max: 280
}
));
}
#[test]
fn validate_id_ok() {
assert!(validate_id("123", "tweet_id").is_ok());
}
#[test]
fn validate_id_empty() {
let err = validate_id("", "tweet_id").unwrap_err();
assert!(matches!(err, ToolkitError::InvalidInput { .. }));
}
#[test]
fn toolkit_error_display() {
let err = ToolkitError::TweetTooLong {
length: 300,
max: 280,
};
assert_eq!(err.to_string(), "tweet too long: 300 characters (max 280)");
}
#[test]
fn toolkit_error_from_x_api() {
let xe = XApiError::ApiError {
status: 404,
message: "Not found".to_string(),
};
let te: ToolkitError = xe.into();
assert!(matches!(te, ToolkitError::XApi(_)));
}
}