1use serde::{Deserialize, Serialize};
2
3const MAX_REQUEST_ID_BYTES: usize = 128;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
7#[serde(rename_all = "kebab-case")]
8pub enum Surface {
9 Mcp,
11 Rest,
13 Cli,
15 Palette,
17}
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
21#[serde(rename_all = "kebab-case")]
22pub enum AuthorizationMode {
23 LoopbackDev,
25 TrustedGateway,
27 Mounted,
29}
30
31#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
33#[serde(rename_all = "kebab-case")]
34pub enum Confirmation {
35 #[default]
37 Missing,
38 Confirmed,
40}
41
42impl Confirmation {
43 pub fn is_confirmed(self) -> bool {
45 matches!(self, Self::Confirmed)
46 }
47}
48
49#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
52#[serde(transparent)]
53pub struct RequestId(String);
54
55impl RequestId {
56 pub fn new(value: impl Into<String>) -> Result<Self, RequestIdError> {
59 let value = value.into();
60 if value.trim().is_empty() {
61 return Err(RequestIdError::Empty);
62 }
63 if value.len() > MAX_REQUEST_ID_BYTES {
64 return Err(RequestIdError::TooLong {
65 actual: value.len(),
66 maximum: MAX_REQUEST_ID_BYTES,
67 });
68 }
69 if value.chars().any(char::is_control) {
70 return Err(RequestIdError::ControlCharacter);
71 }
72 Ok(Self(value))
73 }
74
75 pub fn as_str(&self) -> &str {
77 &self.0
78 }
79}
80
81#[derive(Debug, Clone, PartialEq, Eq)]
83pub enum RequestIdError {
84 Empty,
86 TooLong {
88 actual: usize,
90 maximum: usize,
92 },
93 ControlCharacter,
95}
96
97impl std::fmt::Display for RequestIdError {
98 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
99 match self {
100 Self::Empty => f.write_str("request id must not be empty"),
101 Self::TooLong { actual, maximum } => {
102 write!(f, "request id is {actual} bytes; maximum is {maximum}")
103 }
104 Self::ControlCharacter => f.write_str("request id must not contain control characters"),
105 }
106 }
107}
108
109impl std::error::Error for RequestIdError {}
110
111#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
113pub struct TraceContext {
114 pub traceparent: Option<String>,
116 pub tracestate: Option<String>,
118}
119
120#[cfg(test)]
121mod tests {
122 use super::{Confirmation, RequestId};
123
124 #[test]
125 fn request_ids_reject_empty_and_control_characters() {
126 assert!(RequestId::new(" ").is_err());
127 assert!(RequestId::new("request\n1").is_err());
128 assert_eq!(RequestId::new("request-1").unwrap().as_str(), "request-1");
129 }
130
131 #[test]
132 fn confirmation_defaults_to_missing() {
133 assert!(!Confirmation::default().is_confirmed());
134 assert!(Confirmation::Confirmed.is_confirmed());
135 }
136}