///|
priv struct OpRegistry {
handlers : Map[String, (Json) -> Json raise OpDispatchError]
async_handlers : Map[String, async (AppCommandRequestContext, Json) -> Json]
}
///|
/// Creates an empty op handler registry.
fn OpRegistry::new() -> OpRegistry {
OpRegistry::{ handlers: Map([]), async_handlers: Map([]) }
}
///|
/// Registers a typed op handler that cannot fail explicitly.
fn[Payload : @json.FromJson, Reply : ToJson] OpRegistry::handle(
self : OpRegistry,
name : String,
callback : (Payload) -> Reply raise,
) -> Unit raise OpRegistrationError {
validate_op_name(name)
guard self.handler_name_available(name) else { raise DuplicateName(name~) }
self.handlers.set(name, fn(payload_json) raise OpDispatchError {
let payload : Payload = @json.from_json(payload_json) catch {
err => raise InvalidPayload(name~, detail=err.to_string())
}
try callback(payload) catch {
err => raise HandlerFailed(name~, detail=@debug.render(Repr(err)))
} noraise {
reply => ToJson::to_json(reply)
}
})
}
///|
/// Registers a typed async op handler.
fn[Payload : @json.FromJson, Reply : ToJson] OpRegistry::handle_async(
self : OpRegistry,
name : String,
callback : async (Payload) -> Reply,
) -> Unit raise OpRegistrationError {
validate_op_name(name)
guard self.handler_name_available(name) else { raise DuplicateName(name~) }
self.async_handlers.set(name, async fn(_context, payload_json) {
let payload : Payload = @json.from_json(payload_json) catch {
err => raise InvalidPayload(name~, detail=err.to_string())
}
try callback(payload) catch {
err => raise HandlerFailed(name~, detail=@debug.render(Repr(err)))
} noraise {
reply => ToJson::to_json(reply)
}
})
}
///|
/// Registers a typed async op handler that receives request-scoped context.
fn[Payload : @json.FromJson, Reply : ToJson] OpRegistry::handle_async_with_context(
self : OpRegistry,
name : String,
callback : async (AppCommandRequestContext, Payload) -> Reply,
) -> Unit raise OpRegistrationError {
validate_op_name(name)
guard self.handler_name_available(name) else { raise DuplicateName(name~) }
self.async_handlers.set(name, async fn(context, payload_json) {
let payload : Payload = @json.from_json(payload_json) catch {
err => raise InvalidPayload(name~, detail=err.to_string())
}
try callback(context, payload) catch {
err => raise HandlerFailed(name~, detail=@debug.render(Repr(err)))
} noraise {
reply => ToJson::to_json(reply)
}
})
}
///|
/// Runs a previously registered async op handler on the current async loop.
async fn OpRegistry::call_async_direct(
self : OpRegistry,
name : String,
payload : Json,
) -> Json {
let result : Ref[Json?] = Ref(None)
@async.with_task_group(group => {
result.val = Some(
self.call_async_direct_with_context(
AppCommandRequestContext::new(0L, group),
name,
payload,
),
)
})
result.val.unwrap()
}
///|
/// Runs a previously registered async op handler with request-scoped context.
async fn OpRegistry::call_async_direct_with_context(
self : OpRegistry,
context : AppCommandRequestContext,
name : String,
payload : Json,
) -> Json {
match self.async_handlers.get(name) {
Some(handler) => handler(context, payload)
None => self.call(name, payload)
}
}
///|
/// Returns whether an op name has been registered as an async handler.
fn OpRegistry::has_async(self : OpRegistry, name : String) -> Bool {
self.async_handlers.get(name) is Some(_)
}
///|
/// Invokes a previously registered op handler with raw JSON payload.
fn OpRegistry::call(
self : OpRegistry,
name : String,
payload : Json,
) -> Json raise OpDispatchError {
match self.handlers.get(name) {
Some(handler) => handler(payload)
None => raise UnknownOp(name~)
}
}
///|
fn OpRegistry::handler_name_available(self : OpRegistry, name : String) -> Bool {
self.handlers.get(name) is None && self.async_handlers.get(name) is None
}
///|
fn validate_op_name(name : String) -> Unit raise OpRegistrationError {
guard name.trim().to_owned() != "" else { raise EmptyName }
}