// The request a handler is serving, handed to it beside its arguments: who
// asked, for what, and the way back out to the client while the answer is
// still being worked out.
///|
/// What one request says about itself: the revision it was sent under, the
/// client's identity and capabilities, the progress token it offered, the
/// headers that carried it, and the answers it brings back to an earlier leg.
pub(all) struct Facts {
protocol_version : String
operation : String
request_id : Json
client_info : @types.Implementation?
client_capabilities : @types.ClientCapabilities?
progress_token : Json?
headers : Map[String, String]
input_responses : Map[String, Json]
request_state : String?
/// The subject the transport's token vouched for, when it checked one.
subject : String?
/// Every claim that token carried; empty when there was no token.
claims : Map[String, Json]
} derive(Debug, Eq)
///|
/// The facts of a request that carries nothing but its own name, which is what
/// a handler called straight from a program sees.
pub fn Facts::new(
operation~ : String = "",
request_id~ : Json = Json::null(),
protocol_version~ : String = @types.latest_protocol_version,
client_info? : @types.Implementation,
client_capabilities? : @types.ClientCapabilities,
progress_token? : Json,
headers~ : Map[String, String] = Map([]),
input_responses~ : Map[String, Json] = Map([]),
request_state? : String,
subject? : String,
claims~ : Map[String, Json] = Map([]),
) -> Facts {
{
protocol_version,
operation,
request_id,
client_info,
client_capabilities,
progress_token,
headers,
input_responses,
request_state,
subject,
claims,
}
}
///|
/// The request a tool, a resource or a prompt is serving: the facts above, and
/// the two things a handler can do with the connection while it runs, report
/// progress and ask whether the caller has gone away.
///
/// A `Context` is passed by the framework with every request; a handler called
/// straight from a program gets `Context::quiet`, whose reports go nowhere.
pub struct Context {
priv facts : Facts
priv emit : async (Json) -> Unit
priv gone : () -> Bool
priv logger : @moonlog.Logger?
}
///|
/// Whether a message leaving a handler goes out at all, and whether the caller
/// is still there. Both are supplied by the transport that carries the
/// request; the defaults do nothing.
pub fn Context::new(
facts~ : Facts,
emit~ : async (Json) -> Unit = fn(_) { () },
gone~ : () -> Bool = fn() { false },
logger? : @moonlog.Logger,
) -> Context {
{ facts, emit, gone, logger, }
}
///|
/// A context for a call made without a connection: reports are dropped and the
/// caller is always there.
pub fn Context::quiet() -> Context {
Context::new(facts=Facts::new())
}
///|
/// The facts this request was served with.
pub fn Context::facts(self : Context) -> Facts {
self.facts
}
///|
/// The protocol revision the request was sent under.
pub fn Context::protocol_version(self : Context) -> String {
self.facts.protocol_version
}
///|
/// The method that reached this handler, for example `tools/call`.
pub fn Context::operation(self : Context) -> String {
self.facts.operation
}
///|
/// The JSON-RPC id of the request, which a progress notification or a
/// subscription echoes back.
pub fn Context::request_id(self : Context) -> Json {
self.facts.request_id
}
///|
/// Who the client says it is. Self-reported and unverified, of use for display
/// and for logging, never for a permission decision.
pub fn Context::client_info(self : Context) -> @types.Implementation? {
self.facts.client_info
}
///|
/// What the client says it can do on this request. A request carries its own
/// capabilities, so this is never inherited from an earlier one.
pub fn Context::client_capabilities(
self : Context,
) -> @types.ClientCapabilities? {
self.facts.client_capabilities
}
///|
/// The token the client gave for progress reports, if it asked for any. A
/// handler that reports progress without one is not answered, and the reports
/// go nowhere.
pub fn Context::progress_token(self : Context) -> Json? {
self.facts.progress_token
}
///|
/// The value of the request header `name`, in lower case as ASGI carries it.
/// Over stdio there are none.
pub fn Context::header(self : Context, name : String) -> String? {
self.facts.headers.get(name)
}
///|
/// Every header the request carried, keyed in lower case.
pub fn Context::headers(self : Context) -> Map[String, String] {
self.facts.headers
}
///|
/// The answers a client sent back with a retried request, keyed by the id the
/// server gave each input request.
pub fn Context::input_responses(self : Context) -> Map[String, Json] {
self.facts.input_responses
}
///|
/// The subject a bearer token vouched for, when the transport checked one.
/// `None` over stdio, and over HTTP when the endpoint is open.
pub fn Context::subject(self : Context) -> String? {
self.facts.subject
}
///|
/// Every claim the token carried, as the issuer wrote them. Empty when no
/// token was checked.
pub fn Context::claims(self : Context) -> Map[String, Json] {
self.facts.claims
}
///|
/// The server's log, scoped to this request: its id and operation are already
/// bound, so a line says which request it came from. `None` when the server
/// was given no logger, which is the default.
pub fn Context::logger(self : Context) -> @moonlog.Logger? {
self.logger
}
///|
/// The opaque state the server asked the client to hand back on the retry, or
/// `None` on the first leg.
pub fn Context::request_state(self : Context) -> String? {
self.facts.request_state
}
///|
/// Whether the caller said it no longer wants this answer. A long handler
/// should stop; the framework drops whatever it returns.
pub fn Context::is_cancelled(self : Context) -> Bool {
(self.gone)()
}
///|
/// Report how far along the work is. Silent when the client gave no progress
/// token, as the specification has it: a report the caller did not ask for is
/// not sent.
///
/// `progress` grows towards `total`; a report without `total` says how much is
/// done but not how much is left.
pub async fn Context::report_progress(
self : Context,
progress~ : Double,
total? : Double,
message? : String,
) -> Unit {
guard self.facts.progress_token is Some(token) else { return }
let params : Map[String, Json] = {
"progressToken": token,
"progress": Json::number(progress),
}
if total is Some(value) {
params["total"] = Json::number(value)
}
if message is Some(text) {
params["message"] = Json::string(text)
}
(self.emit)(notification(@types.notification_progress, params))
}
///|
/// Report a log message to the client, at `level`, as `notifications/message`.
/// It travels the same way progress does: on the stream of the request that
/// caused it, and nowhere when the transport has no stream.
pub async fn Context::log(
self : Context,
level~ : @types.LoggingLevel,
message~ : String,
logger? : String,
data? : Json,
) -> Unit {
// The server's own log first: a client that went away should not take the
// record of what happened with it.
if self.logger is Some(log) {
let fields : Array[(String, @moonlog.Value)] = []
if logger is Some(name) {
fields.push(("logger", @moonlog.Value::of_str(name)))
}
if data is Some(value) {
fields.push(("data", @moonlog.Value::of_str(value.stringify())))
}
log.log(severity(level), message, fields~)
}
let params : Map[String, Json] = {
"level": Json::string(level.to_wire()),
"data": Json::string(message),
}
if logger is Some(name) {
params["logger"] = Json::string(name)
}
if data is Some(value) {
params["data"] = value
}
(self.emit)(notification(@types.notification_message, params))
}
///|
/// A log message at `debug`.
pub async fn Context::debug(self : Context, message : String) -> Unit {
self.log(level=@types.Debug, message~)
}
///|
/// A log message at `info`.
pub async fn Context::info(self : Context, message : String) -> Unit {
self.log(level=@types.Info, message~)
}
///|
/// A log message at `warning`.
pub async fn Context::warning(self : Context, message : String) -> Unit {
self.log(level=@types.Warning, message~)
}
///|
/// A log message at `error`.
pub async fn Context::error(self : Context, message : String) -> Unit {
self.log(level=@types.Error, message~)
}
///|
/// One notification message, framed as the wire carries it.
pub fn notification(operation : String, params : Map[String, Json]) -> Json {
{
"jsonrpc": "2.0",
"method": Json::string(operation),
"params": Json(params),
}
}
///|
pub extend Facts with Debug::{to_repr}
///|
pub extend Facts with Eq::{not_equal, equal}
///|
/// The same severity in the server's own log, which has six levels where the
/// protocol has eight.
fn severity(level : @types.LoggingLevel) -> @moonlog.Level {
match level {
@types.Debug => @moonlog.debug
@types.Info | @types.Notice => @moonlog.info
@types.Warning => @moonlog.warn
@types.Error => @moonlog.error
@types.Critical | @types.Alert | @types.Emergency => @moonlog.fatal
}
}