use std::time::Duration;
use serde::{Deserialize, Serialize};
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum ToolEffect {
#[default]
ReadOnly,
Mutating,
Destructive,
}
impl ToolEffect {
#[must_use]
pub const fn as_wire(self) -> &'static str {
match self {
Self::ReadOnly => "read_only",
Self::Mutating => "mutating",
Self::Destructive => "destructive",
}
}
}
impl std::fmt::Display for ToolEffect {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_wire())
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct RetryHint {
pub max_attempts: u32,
pub initial_backoff: Duration,
}
impl RetryHint {
#[must_use]
pub const fn idempotent_transport() -> Self {
Self {
max_attempts: 3,
initial_backoff: Duration::from_millis(200),
}
}
#[must_use]
pub const fn new(max_attempts: u32, initial_backoff: Duration) -> Self {
assert!(max_attempts >= 1, "RetryHint::max_attempts must be >= 1");
Self {
max_attempts,
initial_backoff,
}
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
#[test]
fn effect_default_is_read_only() {
assert_eq!(ToolEffect::default(), ToolEffect::ReadOnly);
}
#[test]
fn effect_wire_strings_are_stable() {
assert_eq!(ToolEffect::ReadOnly.as_wire(), "read_only");
assert_eq!(ToolEffect::Mutating.as_wire(), "mutating");
assert_eq!(ToolEffect::Destructive.as_wire(), "destructive");
}
#[test]
fn effect_serde_round_trip() {
let s = serde_json::to_string(&ToolEffect::Destructive).unwrap();
assert_eq!(s, "\"destructive\"");
let back: ToolEffect = serde_json::from_str(&s).unwrap();
assert_eq!(back, ToolEffect::Destructive);
}
#[test]
fn retry_hint_const_ctor_baseline() {
let h = RetryHint::idempotent_transport();
assert_eq!(h.max_attempts, 3);
assert_eq!(h.initial_backoff, Duration::from_millis(200));
}
#[test]
#[should_panic(expected = "RetryHint::max_attempts must be >= 1")]
fn retry_hint_zero_attempts_panics() {
let _ = RetryHint::new(0, Duration::from_millis(100));
}
}