use core::fmt;
use core::str::FromStr;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case", tag = "direction", content = "name")]
pub enum AcpMethod {
Agent(AgentMethod),
Client(ClientMethod),
Protocol(ProtocolMethod),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AgentMethod {
Initialize,
Authenticate,
SessionNew,
SessionLoad,
SessionSetMode,
SessionSetConfigOption,
SessionPrompt,
SessionCancel,
SessionList,
SessionDelete,
SessionResume,
SessionClose,
Logout,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ClientMethod {
SessionRequestPermission,
SessionUpdate,
FsWriteTextFile,
FsReadTextFile,
TerminalCreate,
TerminalOutput,
TerminalRelease,
TerminalWaitForExit,
TerminalKill,
ElicitationCreate,
ElicitationComplete,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ProtocolMethod {
CancelRequest,
}
impl AgentMethod {
pub const fn as_wire_name(self) -> &'static str {
match self {
Self::Initialize => "initialize",
Self::Authenticate => "authenticate",
Self::SessionNew => "session/new",
Self::SessionLoad => "session/load",
Self::SessionSetMode => "session/set_mode",
Self::SessionSetConfigOption => "session/set_config_option",
Self::SessionPrompt => "session/prompt",
Self::SessionCancel => "session/cancel",
Self::SessionList => "session/list",
Self::SessionDelete => "session/delete",
Self::SessionResume => "session/resume",
Self::SessionClose => "session/close",
Self::Logout => "logout",
}
}
}
impl ClientMethod {
pub const fn as_wire_name(self) -> &'static str {
match self {
Self::SessionRequestPermission => "session/request_permission",
Self::SessionUpdate => "session/update",
Self::FsWriteTextFile => "fs/write_text_file",
Self::FsReadTextFile => "fs/read_text_file",
Self::TerminalCreate => "terminal/create",
Self::TerminalOutput => "terminal/output",
Self::TerminalRelease => "terminal/release",
Self::TerminalWaitForExit => "terminal/wait_for_exit",
Self::TerminalKill => "terminal/kill",
Self::ElicitationCreate => "elicitation/create",
Self::ElicitationComplete => "elicitation/complete",
}
}
}
impl ProtocolMethod {
pub const fn as_wire_name(self) -> &'static str {
match self {
Self::CancelRequest => "$/cancel_request",
}
}
}
impl AcpMethod {
pub const fn as_wire_name(self) -> &'static str {
match self {
Self::Agent(method) => method.as_wire_name(),
Self::Client(method) => method.as_wire_name(),
Self::Protocol(method) => method.as_wire_name(),
}
}
}
impl fmt::Display for AcpMethod {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.as_wire_name())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AcpMethodParseError {
input: String,
}
impl fmt::Display for AcpMethodParseError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
formatter,
"ACP method {:?} is not in the v1.25.0 method table",
self.input
)
}
}
impl std::error::Error for AcpMethodParseError {}
impl FromStr for AcpMethod {
type Err = AcpMethodParseError;
fn from_str(input: &str) -> Result<Self, Self::Err> {
if let Some(rest) = input.strip_prefix("$/") {
return match rest {
"cancel_request" => Ok(Self::Protocol(ProtocolMethod::CancelRequest)),
_ => Err(AcpMethodParseError {
input: input.to_owned(),
}),
};
}
for method in [
Self::Agent(AgentMethod::Initialize),
Self::Agent(AgentMethod::Authenticate),
Self::Agent(AgentMethod::SessionNew),
Self::Agent(AgentMethod::SessionLoad),
Self::Agent(AgentMethod::SessionSetMode),
Self::Agent(AgentMethod::SessionSetConfigOption),
Self::Agent(AgentMethod::SessionPrompt),
Self::Agent(AgentMethod::SessionCancel),
Self::Agent(AgentMethod::SessionList),
Self::Agent(AgentMethod::SessionDelete),
Self::Agent(AgentMethod::SessionResume),
Self::Agent(AgentMethod::SessionClose),
Self::Agent(AgentMethod::Logout),
] {
if method.as_wire_name() == input {
return Ok(method);
}
}
for method in [
Self::Client(ClientMethod::SessionRequestPermission),
Self::Client(ClientMethod::SessionUpdate),
Self::Client(ClientMethod::FsWriteTextFile),
Self::Client(ClientMethod::FsReadTextFile),
Self::Client(ClientMethod::TerminalCreate),
Self::Client(ClientMethod::TerminalOutput),
Self::Client(ClientMethod::TerminalRelease),
Self::Client(ClientMethod::TerminalWaitForExit),
Self::Client(ClientMethod::TerminalKill),
Self::Client(ClientMethod::ElicitationCreate),
Self::Client(ClientMethod::ElicitationComplete),
] {
if method.as_wire_name() == input {
return Ok(method);
}
}
Err(AcpMethodParseError {
input: input.to_owned(),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn every_meta_method_round_trips_through_wire_name() -> Result<(), AcpMethodParseError> {
let cases: &[(AcpMethod, &str)] = &[
(AcpMethod::Agent(AgentMethod::Initialize), "initialize"),
(AcpMethod::Agent(AgentMethod::Authenticate), "authenticate"),
(AcpMethod::Agent(AgentMethod::SessionNew), "session/new"),
(AcpMethod::Agent(AgentMethod::SessionLoad), "session/load"),
(
AcpMethod::Agent(AgentMethod::SessionSetMode),
"session/set_mode",
),
(
AcpMethod::Agent(AgentMethod::SessionSetConfigOption),
"session/set_config_option",
),
(
AcpMethod::Agent(AgentMethod::SessionPrompt),
"session/prompt",
),
(
AcpMethod::Agent(AgentMethod::SessionCancel),
"session/cancel",
),
(AcpMethod::Agent(AgentMethod::SessionList), "session/list"),
(
AcpMethod::Agent(AgentMethod::SessionDelete),
"session/delete",
),
(
AcpMethod::Agent(AgentMethod::SessionResume),
"session/resume",
),
(AcpMethod::Agent(AgentMethod::SessionClose), "session/close"),
(AcpMethod::Agent(AgentMethod::Logout), "logout"),
(
AcpMethod::Client(ClientMethod::SessionRequestPermission),
"session/request_permission",
),
(
AcpMethod::Client(ClientMethod::SessionUpdate),
"session/update",
),
(
AcpMethod::Client(ClientMethod::FsWriteTextFile),
"fs/write_text_file",
),
(
AcpMethod::Client(ClientMethod::FsReadTextFile),
"fs/read_text_file",
),
(
AcpMethod::Client(ClientMethod::TerminalCreate),
"terminal/create",
),
(
AcpMethod::Client(ClientMethod::TerminalOutput),
"terminal/output",
),
(
AcpMethod::Client(ClientMethod::TerminalRelease),
"terminal/release",
),
(
AcpMethod::Client(ClientMethod::TerminalWaitForExit),
"terminal/wait_for_exit",
),
(
AcpMethod::Client(ClientMethod::TerminalKill),
"terminal/kill",
),
(
AcpMethod::Client(ClientMethod::ElicitationCreate),
"elicitation/create",
),
(
AcpMethod::Client(ClientMethod::ElicitationComplete),
"elicitation/complete",
),
(
AcpMethod::Protocol(ProtocolMethod::CancelRequest),
"$/cancel_request",
),
];
assert_eq!(
cases.len(),
25,
"every published ACP v1 method should appear"
);
for (method, expected_name) in cases {
assert_eq!(method.as_wire_name(), *expected_name);
let round_tripped: AcpMethod = expected_name.parse()?;
assert_eq!(
round_tripped, *method,
"round-trip failed for {expected_name}"
);
}
Ok(())
}
#[test]
fn an_unknown_wire_name_is_an_error() {
let Err(error) = "session/unicorn".parse::<AcpMethod>() else {
return;
};
assert_eq!(error.input, "session/unicorn");
}
}