1use super::framing::{self, INVALID_PARAMS, METHOD_NOT_FOUND};
4use super::negotiate::{Era, Refusal, Session, shaped};
5use super::tools::{TOOLS_TTL_MS, ToolResult, object, string, tool_table};
6use crate::network::{Rail, call, directory, flow};
7use serde_json::{Map, Value, json};
8
9#[derive(Debug)]
11pub struct Server<R> {
12 rail: R,
13 session: Session,
14 pub(super) frame_bytes: usize,
15 pub(super) subscriptions: std::sync::Arc<super::subscriptions::Subscriptions>,
16 pub(super) warning_only: std::sync::Arc<std::sync::atomic::AtomicBool>,
17}
18
19impl<R: Rail> Server<R> {
20 #[must_use]
22 pub fn new(rail: R, frame_bytes: usize) -> Self {
23 Self {
24 rail,
25 session: Session::new(),
26 frame_bytes,
27 subscriptions: std::sync::Arc::default(),
28 warning_only: std::sync::Arc::default(),
29 }
30 }
31
32 #[must_use]
34 pub const fn rail(&self) -> &R {
35 &self.rail
36 }
37
38 pub fn answer(&mut self, id: &Value, method: &str, params: &Map<String, Value>) -> Value {
40 match self.respond(method, params) {
41 Ok(result) => framing::result(id, &result),
42 Err(refusal) => framing::error(id, refusal.code, &refusal.message, refusal.data),
43 }
44 }
45
46 fn respond(&mut self, method: &str, params: &Map<String, Value>) -> Result<Value, Refusal> {
47 match method {
48 "initialize" => return self.session.initialize(params),
49 "server/discover" => {
50 self.session.era_of(params)?;
51 return Ok(Session::discover());
52 }
53 _ => {}
54 }
55 let era = self.session.era_of(params)?;
56 match method {
57 "ping" => Ok(shaped(era, Map::new())),
58 "logging/setLevel" => {
59 super::notifications::set_level(params, &self.warning_only)?;
60 Ok(shaped(era, Map::new()))
61 }
62 "tools/list" => {
63 let mut result = Map::new();
64 result.insert("tools".to_owned(), tool_table());
65 if era == Era::Modern {
66 result.insert("ttlMs".to_owned(), json!(TOOLS_TTL_MS));
71 result.insert("cacheScope".to_owned(), json!("public"));
72 }
73 Ok(shaped(era, result))
74 }
75 "tools/call" => {
76 let name = params
77 .get("name")
78 .and_then(Value::as_str)
79 .ok_or_else(|| Refusal {
80 code: INVALID_PARAMS,
81 message: "the request is missing name".to_owned(),
82 data: None,
83 })?;
84 let arguments = params
85 .get("arguments")
86 .and_then(Value::as_object)
87 .cloned()
88 .unwrap_or_default();
89 let outcome = self.tool(name, &arguments)?;
90 Ok(outcome.into_result(era))
91 }
92 other => Err(Refusal {
93 code: METHOD_NOT_FOUND,
94 message: format!("the ticket has no method {other}"),
95 data: None,
96 }),
97 }
98 }
99
100 pub fn tool(&self, name: &str, arguments: &Map<String, Value>) -> Result<ToolResult, Refusal> {
105 if matches!(name, "subscribe" | "unsubscribe") {
106 let service = super::subscriptions::service_argument(arguments)?;
107 return Ok(self.subscriptions.tool(&self.rail, name, service));
108 }
109 if let Some(outcome) = crate::network::lifecycle::tool(&self.rail, name, arguments) {
110 return Ok(outcome);
111 }
112 let outcome = match name {
113 "list" => directory::list(&self.rail),
114 "describe" => {
115 let subject = string(arguments, "subject")?;
116 directory::describe(&self.rail, subject)
117 }
118 "call" => {
119 let subject = string(arguments, "subject")?;
120 let input = object(arguments, "input")?;
121 let reply = arguments
122 .get("reply")
123 .and_then(Value::as_bool)
124 .unwrap_or(true);
125 let pass_for = match arguments.get("pass_for") {
126 None => None,
127 Some(Value::String(audience)) => Some(audience.as_str()),
128 Some(_) => {
129 return Err(Refusal {
130 code: INVALID_PARAMS,
131 message: "the tool call's pass_for is not a string".to_owned(),
132 data: None,
133 });
134 }
135 };
136 call::call(&self.rail, subject, input, reply, pass_for)
137 }
138 "flow" => {
139 let steps = arguments
140 .get("steps")
141 .and_then(Value::as_array)
142 .ok_or_else(|| Refusal {
143 code: INVALID_PARAMS,
144 message: "the flow is missing steps".to_owned(),
145 data: None,
146 })?;
147 flow::flow(&self.rail, steps)
148 }
149 other => {
150 return Err(Refusal {
151 code: METHOD_NOT_FOUND,
152 message: format!("the ticket has no tool {other}"),
153 data: None,
154 });
155 }
156 };
157 Ok(outcome)
158 }
159}