1use crate::rpc::{self, Id, Response};
28use serde_json::{Value, json};
29
30#[derive(Debug, Clone)]
32pub enum Inbound {
33 Elicit {
36 message: String,
37 requested_schema: Value,
38 },
39 ListRoots,
41}
42
43#[derive(Debug, Clone)]
46pub enum Answer {
47 Accept(Value),
49 Decline,
51 Cancel,
53 Roots(Vec<Root>),
55}
56
57#[derive(Debug, Clone)]
59pub struct Root {
60 pub uri: String,
61 pub name: Option<String>,
62}
63
64pub trait Handler: Send + Sync {
67 fn handle(&self, req: Inbound) -> Option<Answer>;
70}
71
72#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
76pub struct Capabilities {
77 pub elicitation: bool,
78 pub roots: bool,
79}
80
81impl Capabilities {
82 pub fn to_json(self) -> Value {
85 let mut caps = serde_json::Map::new();
86 if self.elicitation {
87 caps.insert("elicitation".into(), json!({}));
88 }
89 if self.roots {
90 caps.insert("roots".into(), json!({"listChanged": false}));
94 }
95 Value::Object(caps)
96 }
97
98 pub fn is_empty(self) -> bool {
99 !self.elicitation && !self.roots
100 }
101}
102
103pub fn answer(req: &rpc::Request, caps: Capabilities, handler: Option<&dyn Handler>) -> Response {
109 let id = req.id.clone();
110 match req.method.as_str() {
111 "ping" => Response::ok(id, json!({})),
113
114 "elicitation/create" if caps.elicitation => {
115 let params = req.params.clone().unwrap_or(Value::Null);
116 let message = params
117 .get("message")
118 .and_then(Value::as_str)
119 .unwrap_or("")
120 .to_string();
121 let requested_schema = params
122 .get("requestedSchema")
123 .cloned()
124 .unwrap_or_else(|| json!({"type": "object"}));
125 match handler.and_then(|h| {
126 h.handle(Inbound::Elicit {
127 message,
128 requested_schema,
129 })
130 }) {
131 Some(Answer::Accept(content)) => {
132 Response::ok(id, json!({"action": "accept", "content": content}))
133 }
134 Some(Answer::Decline) => Response::ok(id, json!({"action": "decline"})),
135 Some(Answer::Cancel) | None => Response::ok(id, json!({"action": "cancel"})),
137 Some(Answer::Roots(_)) => Response::err(
138 id,
139 rpc::INTERNAL_ERROR,
140 "handler answered elicitation with roots",
141 ),
142 }
143 }
144
145 "roots/list" if caps.roots => match handler.and_then(|h| h.handle(Inbound::ListRoots)) {
146 Some(Answer::Roots(roots)) => Response::ok(
147 id,
148 json!({
149 "roots": roots.iter().map(|r| match &r.name {
150 Some(n) => json!({"uri": r.uri, "name": n}),
151 None => json!({"uri": r.uri}),
152 }).collect::<Vec<_>>()
153 }),
154 ),
155 _ => Response::ok(id, json!({"roots": []})),
156 },
157
158 other => Response::err(
162 id,
163 rpc::METHOD_NOT_FOUND,
164 format!("client does not implement {other}"),
165 ),
166 }
167}
168
169pub fn as_request(v: &Value) -> Option<rpc::Request> {
173 if v.get("method").is_some() && v.get("id").is_some() {
174 serde_json::from_value::<rpc::Request>(v.clone()).ok()
175 } else {
176 None
177 }
178}
179
180pub fn frame_id(v: &Value) -> Option<Id> {
182 serde_json::from_value::<Id>(v.get("id")?.clone()).ok()
183}
184
185#[cfg(test)]
186mod tests {
187 use super::*;
188
189 struct Yes(Answer);
190 impl Handler for Yes {
191 fn handle(&self, _req: Inbound) -> Option<Answer> {
192 Some(self.0.clone())
193 }
194 }
195 struct No;
196 impl Handler for No {
197 fn handle(&self, _req: Inbound) -> Option<Answer> {
198 None
199 }
200 }
201
202 fn req(method: &str, params: Value) -> rpc::Request {
203 rpc::Request::new(1, method, Some(params))
204 }
205
206 #[test]
207 fn ping_is_answered_even_with_no_handler_or_capabilities() {
208 let r = answer(&req("ping", json!({})), Capabilities::default(), None);
211 assert_eq!(r.result, Some(json!({})));
212 assert!(r.error.is_none());
213 }
214
215 #[test]
216 fn elicitation_maps_the_three_outcomes() {
217 let caps = Capabilities {
218 elicitation: true,
219 roots: false,
220 };
221 let p = json!({"message": "Which environment?", "requestedSchema": {"type": "object"}});
222
223 let accept = Yes(Answer::Accept(json!({"env": "staging"})));
224 let r = answer(&req("elicitation/create", p.clone()), caps, Some(&accept));
225 assert_eq!(r.result.as_ref().unwrap()["action"], "accept");
226 assert_eq!(r.result.unwrap()["content"]["env"], "staging");
227
228 let decline = Yes(Answer::Decline);
230 let r = answer(&req("elicitation/create", p.clone()), caps, Some(&decline));
231 assert!(r.error.is_none());
232 assert_eq!(r.result.unwrap()["action"], "decline");
233
234 let r = answer(&req("elicitation/create", p.clone()), caps, Some(&No));
236 assert_eq!(r.result.unwrap()["action"], "cancel");
237 let r = answer(&req("elicitation/create", p), caps, None);
238 assert_eq!(r.result.unwrap()["action"], "cancel");
239 }
240
241 #[test]
242 fn an_undeclared_capability_is_refused_not_half_answered() {
243 let none = Capabilities::default();
245 let r = answer(&req("elicitation/create", json!({})), none, Some(&No));
246 assert_eq!(r.error.as_ref().unwrap().code, rpc::METHOD_NOT_FOUND);
247 let r = answer(&req("roots/list", json!({})), none, Some(&No));
248 assert_eq!(r.error.as_ref().unwrap().code, rpc::METHOD_NOT_FOUND);
249 let r = answer(&req("sampling/createMessage", json!({})), none, None);
251 assert_eq!(r.error.unwrap().code, rpc::METHOD_NOT_FOUND);
252 }
253
254 #[test]
255 fn roots_are_listed_when_declared() {
256 let caps = Capabilities {
257 elicitation: false,
258 roots: true,
259 };
260 let h = Yes(Answer::Roots(vec![Root {
261 uri: "file:///work".into(),
262 name: Some("workspace".into()),
263 }]));
264 let r = answer(&req("roots/list", json!({})), caps, Some(&h));
265 let roots = &r.result.unwrap()["roots"];
266 assert_eq!(roots[0]["uri"], "file:///work");
267 assert_eq!(roots[0]["name"], "workspace");
268 }
269
270 #[test]
271 fn capabilities_serialize_to_what_we_can_actually_answer() {
272 assert_eq!(Capabilities::default().to_json(), json!({}));
273 assert!(Capabilities::default().is_empty());
274 let both = Capabilities {
275 elicitation: true,
276 roots: true,
277 };
278 assert_eq!(
279 both.to_json(),
280 json!({"elicitation": {}, "roots": {"listChanged": false}})
281 );
282 assert!(both.to_json().get("ping").is_none());
284 }
285
286 #[test]
287 fn request_classification_separates_requests_from_notifications() {
288 assert!(as_request(&json!({"jsonrpc":"2.0","id":1,"method":"ping"})).is_some());
289 assert!(as_request(&json!({"jsonrpc":"2.0","method":"notifications/x"})).is_none());
291 assert!(as_request(&json!({"jsonrpc":"2.0","id":1,"result":{}})).is_none());
293 }
294}