1use crate::forge::BoxFuture;
10use escouade_plugin_protocol::{code, greeted, initialized, method};
11use parking_lot::Mutex;
12use serde_json::{json, Value};
13use std::collections::{HashMap, HashSet};
14use std::future::poll_fn;
15use std::sync::atomic::{AtomicU64, Ordering};
16use std::sync::Arc;
17use std::task::{Poll, Waker};
18
19#[cfg(not(target_family = "wasm"))]
20mod process;
21#[cfg(any(test, target_family = "wasm"))]
22pub(crate) mod reactor;
23
24#[cfg(not(target_family = "wasm"))]
25pub use process::{run, serve_on};
26#[cfg(target_family = "wasm")]
27pub use reactor::run;
28
29pub use escouade_plugin_protocol::Refusal;
31
32pub trait Plugin: Send + Sync + 'static {
34 fn capabilities(&self) -> Value {
36 json!({})
37 }
38
39 fn call<'a>(
40 &'a self,
41 cx: &'a Cx,
42 method: &'a str,
43 params: Value,
44 ) -> BoxFuture<'a, Result<Value, Refusal>>;
45}
46
47#[derive(Default)]
49struct Waiting {
50 answer: Option<Result<Value, Refusal>>,
51 waker: Option<Waker>,
52}
53
54type Out = Box<dyn Fn(String) -> bool + Send + Sync>;
56
57pub struct Cx {
59 out: Out,
60 pending: Mutex<HashMap<u64, Arc<Mutex<Waiting>>>>,
61 next: AtomicU64,
62 fired: Mutex<HashSet<String>>,
64}
65
66impl Cx {
67 fn new(out: impl Fn(String) -> bool + Send + Sync + 'static) -> Self {
68 Self {
69 out: Box::new(out),
70 pending: Mutex::new(HashMap::new()),
71 next: AtomicU64::new(1),
72 fired: Mutex::new(HashSet::new()),
73 }
74 }
75
76 fn send(&self, line: String) -> bool {
77 (self.out)(line)
78 }
79
80 pub async fn request(&self, method: &str, params: Value) -> Result<Value, Refusal> {
82 let id = self.next.fetch_add(1, Ordering::Relaxed);
83 let waiting = Arc::new(Mutex::new(Waiting::default()));
84 self.pending.lock().insert(id, waiting.clone());
85 let line = json!({ "jsonrpc": "2.0", "id": id, "method": method, "params": params });
86 if !self.send(line.to_string()) {
87 self.pending.lock().remove(&id);
88 return Err(Refusal {
89 code: -32000,
90 message: "Escouade is not answering".into(),
91 });
92 }
93 poll_fn(move |task| {
94 let mut w = waiting.lock();
95 match w.answer.take() {
96 Some(answer) => Poll::Ready(answer),
97 None => {
98 w.waker = Some(task.waker().clone());
99 Poll::Pending
100 }
101 }
102 })
103 .await
104 }
105
106 pub async fn secret(&self, key: &str) -> Result<Option<String>, Refusal> {
108 let v = self.request("secrets/get", json!({ "key": key })).await?;
109 Ok(v.as_str().map(str::to_string))
110 }
111
112 pub fn notify(&self, method: &str, params: Value) {
114 let line = json!({ "jsonrpc": "2.0", "method": method, "params": params });
115 self.send(line.to_string());
116 }
117
118 pub fn log(&self, level: &str, message: &str) {
120 self.notify("log", json!({ "level": level, "message": message }));
121 }
122
123 #[cfg(any(test, target_family = "wasm"))]
125 pub(crate) fn rang(&self, key: &str) -> bool {
126 self.fired.lock().remove(key)
127 }
128
129 fn answered(&self, msg: &Value) {
130 let Some(id) = msg.get("id").and_then(Value::as_u64) else {
131 return;
132 };
133 let Some(waiting) = self.pending.lock().remove(&id) else {
134 return;
135 };
136 let answer = match msg.get("error") {
137 Some(e) => Err(Refusal {
138 code: e["code"].as_i64().unwrap_or(-32000),
139 message: e["message"].as_str().unwrap_or("").to_string(),
140 }),
141 None => Ok(msg.get("result").cloned().unwrap_or(Value::Null)),
142 };
143 let mut w = waiting.lock();
144 w.answer = Some(answer);
145 if let Some(waker) = w.waker.take() {
146 waker.wake();
147 }
148 }
149}
150
151fn answer(id: &Value, result: Result<Value, Refusal>) -> Value {
152 match result {
153 Ok(result) => json!({ "jsonrpc": "2.0", "id": id, "result": result }),
154 Err(e) => json!({
155 "jsonrpc": "2.0",
156 "id": id,
157 "error": { "code": e.code, "message": e.message },
158 }),
159 }
160}
161
162fn parse_error() -> Refusal {
163 Refusal {
164 code: code::PARSE,
165 message: "not a JSON-RPC request".into(),
166 }
167}
168
169fn is_answer(m: &Value) -> bool {
171 m.get("method").is_none() && m.get("id").is_some()
172}
173
174fn arrived(cx: &Cx, line: &str) -> Option<Value> {
177 if line.trim().is_empty() {
178 return None;
179 }
180 let Ok(msg) = serde_json::from_str::<Value>(line) else {
181 cx.send(answer(&Value::Null, Err(parse_error())).to_string());
182 return None;
183 };
184 if is_answer(&msg) {
185 cx.answered(&msg);
186 return None;
187 }
188 if let Value::Array(items) = &msg {
189 if !items.is_empty() && items.iter().all(is_answer) {
190 items.iter().for_each(|m| cx.answered(m));
191 return None;
192 }
193 }
194 if msg["method"] == "timer/fired" {
195 cx.fired.lock().insert(msg["params"]["timer"].to_string());
196 return None;
197 }
198 Some(msg)
199}
200
201async fn one<P: Plugin + ?Sized>(plugin: &P, cx: &Cx, msg: Value) -> (Option<Value>, bool) {
203 let id = msg.get("id").cloned();
204 let Some(name) = msg
205 .get("method")
206 .and_then(Value::as_str)
207 .map(str::to_string)
208 else {
209 return (Some(answer(&Value::Null, Err(parse_error()))), false);
210 };
211 let params = msg.get("params").cloned().unwrap_or(Value::Null);
212 let result = match name.as_str() {
213 method::INITIALIZE => {
214 greeted(¶ms);
215 crate::i18n::from_greeting();
216 Ok(initialized(plugin.capabilities()))
217 }
218 method::SHUTDOWN => Ok(Value::Null),
219 other => plugin.call(cx, other, params).await,
220 };
221 (id.map(|id| answer(&id, result)), name == method::SHUTDOWN)
222}
223
224async fn called<P: Plugin + ?Sized>(plugin: &P, cx: &Cx, msg: Value) -> (Option<Value>, bool) {
226 match msg {
227 Value::Array(items) => {
228 let mut out = Vec::new();
229 let mut shut = false;
230 for m in items {
231 let (a, s) = one(plugin, cx, m).await;
232 out.extend(a);
233 shut |= s;
234 }
235 ((!out.is_empty()).then_some(Value::Array(out)), shut)
236 }
237 m => one(plugin, cx, m).await,
238 }
239}