///|
/// The method table: what a side does with the calls and notices it receives. Registering a name
/// again replaces it.
pub struct Methods {
calls : Map[String, async (Json?) -> Json]
notices : Map[String, async (Json?) -> Unit]
on_error : ((String, Error) -> Unit)?
}
///|
/// A table with nothing in it. `on_error` hears the errors a client is not told about: a handler's
/// failure other than `Failed`, which goes out as -32603 with no detail, and any failure of a notice,
/// which has no one to go to.
pub fn Methods::new(on_error? : (String, Error) -> Unit) -> Methods {
{ calls: {}, notices: {}, on_error, }
}
///|
pub fn Methods::call(
self : Methods,
name : String,
run : async (Json?) -> Json,
) -> Unit {
self.calls[name] = run
}
///|
pub fn Methods::notice(
self : Methods,
name : String,
run : async (Json?) -> Unit,
) -> Unit {
self.notices[name] = run
}
///|
/// A call whose parameters and result go through `FromJson` and `ToJson`. Parameters that do not
/// decode are -32602, with the decoder's reason as the error's `data`.
pub fn[P : @json.FromJson, R : ToJson] Methods::typed(
self : Methods,
name : String,
run : async (P) -> R,
) -> Unit {
self.call(name, async fn(params) {
let input : P = @json.from_json(params.unwrap_or(Json::null())) catch {
error =>
fail(
invalid_params,
"Invalid params",
data=Json::string(error.to_string()),
)
}
run(input).to_json()
})
}
///|
fn Methods::heard(self : Methods, name : String, error : Error) -> Unit {
if self.on_error is Some(on_error) {
on_error(name, error)
}
}
///|
/// What to send back for a message: the response to a call, nothing for the rest. A method nobody
/// registered is -32601 and a raised `Failed` goes out as it is; any other error is -32603 with no
/// detail, because what a handler trips over is not the caller's to read.
pub async fn Methods::answer(self : Methods, message : Message) -> Message? {
match message {
Call(id~, name~, params~) => {
guard self.calls.get(name) is Some(run) else {
return Some(
Refusal(id~, failure={
code: method_not_found,
message: "Method not found",
data: None,
}),
)
}
try run(params) catch {
Failed(failure) => Some(Refusal(id~, failure~))
error => {
self.heard(name, error)
Some(
Refusal(id~, failure={
code: internal_error,
message: "Internal error",
data: None,
}),
)
}
} noraise {
result => Some(Reply(id~, result~))
}
}
Notice(name~, params~) => {
if self.notices.get(name) is Some(run) {
run(params) catch {
error => self.heard(name, error)
}
}
None
}
Reply(_) | Refusal(_) => None
}
}
///|
async fn Methods::answer_json(self : Methods, json : Json) -> Json? {
match message_of(json) {
None => Some(Message::invalid(json).to_json())
Some(message) => self.answer(message).map(answer => answer.to_json())
}
}
///|
/// One body in, one body out, by the specification: text that is not JSON is -32700, a value that is
/// not a message is -32600, an array is a batch, and when nothing needs answering there is nothing to
/// send. `batch=false` is for protocols that forbid arrays, as ACP does: an array is then -32600.
pub async fn Methods::body(
self : Methods,
text : StringView,
batch~ : Bool = true,
) -> String? {
let json = @json.parse(text, preserve_number_repr=true) catch {
_ => return Some(Message::unreadable().to_json().stringify())
}
match json {
Array(items) => {
guard batch && !items.is_empty() else {
return Some(Message::invalid(Json::null()).to_json().stringify())
}
let out = []
for item in items {
if self.answer_json(item) is Some(answer) {
out.push(answer)
}
}
if out.is_empty() {
None
} else {
Some(Json::array(out).stringify())
}
}
one => self.answer_json(one).map(answer => answer.stringify())
}
}