Skip to main content

llm_trait/
backend.rs

1//! Protocol type definitions.
2
3use std::fmt;
4use std::str::FromStr;
5
6/// Wire protocol type.
7///
8/// Describes the API format used to communicate with the LLM provider.
9/// This is distinct from the provider name (e.g., "deepseek" uses OpenAi protocol).
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum Protocol {
12    /// OpenAI Chat Completions API
13    OpenAi,
14    /// OpenAI Responses API (future)
15    OpenAiResponses,
16    /// Anthropic Messages API
17    Anthropic,
18}
19
20/// Error returned when parsing an invalid protocol string.
21#[derive(Debug, Clone)]
22pub struct ProtocolParseError(String);
23
24impl fmt::Display for ProtocolParseError {
25    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
26        write!(f, "unknown protocol: '{}'", self.0)
27    }
28}
29
30impl std::error::Error for ProtocolParseError {}
31
32impl FromStr for Protocol {
33    type Err = ProtocolParseError;
34
35    fn from_str(s: &str) -> Result<Self, Self::Err> {
36        match s.to_lowercase().as_str() {
37            "openai" | "openai-chat" => Ok(Self::OpenAi),
38            "openai-responses" | "responses" => Ok(Self::OpenAiResponses),
39            "anthropic" | "claude" => Ok(Self::Anthropic),
40            _ => Err(ProtocolParseError(s.to_string())),
41        }
42    }
43}
44
45impl Protocol {
46    pub fn as_str(&self) -> &str {
47        match self {
48            Self::OpenAi => "openai",
49            Self::OpenAiResponses => "openai-responses",
50            Self::Anthropic => "anthropic",
51        }
52    }
53}
54
55#[cfg(test)]
56mod tests {
57    use super::*;
58
59    #[test]
60    fn protocol_from_str() {
61        assert_eq!("openai".parse::<Protocol>().unwrap(), Protocol::OpenAi);
62        assert_eq!("openai-chat".parse::<Protocol>().unwrap(), Protocol::OpenAi);
63        assert_eq!(
64            "anthropic".parse::<Protocol>().unwrap(),
65            Protocol::Anthropic
66        );
67        assert_eq!("claude".parse::<Protocol>().unwrap(), Protocol::Anthropic);
68        assert_eq!(
69            "openai-responses".parse::<Protocol>().unwrap(),
70            Protocol::OpenAiResponses
71        );
72        assert_eq!(
73            "responses".parse::<Protocol>().unwrap(),
74            Protocol::OpenAiResponses
75        );
76        assert!("unknown".parse::<Protocol>().is_err());
77    }
78
79    #[test]
80    fn protocol_as_str() {
81        assert_eq!(Protocol::OpenAi.as_str(), "openai");
82        assert_eq!(Protocol::Anthropic.as_str(), "anthropic");
83        assert_eq!(Protocol::OpenAiResponses.as_str(), "openai-responses");
84    }
85
86    #[test]
87    fn protocol_parse_error_display_names_input() {
88        let err = "grpc".parse::<Protocol>().unwrap_err();
89        assert_eq!(err.to_string(), "unknown protocol: 'grpc'");
90        // Usable as a std error and still Debug-printable.
91        let dyn_err: &dyn std::error::Error = &err;
92        assert!(dyn_err.to_string().contains("grpc"));
93    }
94
95    #[test]
96    fn protocol_from_str_case_insensitive() {
97        assert_eq!("OpenAI".parse::<Protocol>().unwrap(), Protocol::OpenAi);
98        assert_eq!(
99            "ANTHROPIC".parse::<Protocol>().unwrap(),
100            Protocol::Anthropic
101        );
102    }
103}