///|
/// Why a shim ended without a result file. `serve` raises it, and an
/// `async fn main` that lets it escape prints the message on stderr and
/// exits nonzero — the runner then reports that the child left no result.
priv suberror NoResult {
NoResult(String)
}
///|
impl Show for NoResult with fn output(self, logger) {
let NoResult(message) = self
logger.write_string(message)
}
///|
/// A whole shim, as `main`: parse argv into `Options`, read the request
/// line from stdin, hand both to `run`, and write the `ShimResult` it
/// returns to the `--result-file` path (the child contract). stdin stays
/// open afterwards as the parent's cancel channel, which `run_cli` watches.
///
/// Every ending the shim controls writes a result: an error `run` raises is
/// a `failed` result, and so is a malformed request that names its
/// `request_id`. With no result path to write to, no request line, or a
/// malformed request whose id is unknown, `serve` raises instead — let it
/// escape `main`, which prints it and exits nonzero.
pub async fn serve(
default_command~ : String,
run~ : async (Options, Request) -> ShimResult,
) -> Unit {
let options = match Options::parse(@env.args()[1:], default_command~) {
Ok(options) => options
Err(problem) => raise NoResult(problem)
}
let line = @stdio.stdin.read_until("\n")
respond(options, line, name="\{default_command} shim", run~)
}
///|
/// `serve` after argv: answer the request `line` (`None` for stdin closed
/// before one arrived) into `options.result_file`. `name` prefixes the
/// diagnostics printed on stderr.
async fn respond(
options : Options,
line : String?,
name~ : String,
run~ : async (Options, Request) -> ShimResult,
) -> Unit {
guard line is Some(line) else {
raise NoResult("\{name}: stdin closed before a request arrived")
}
let (request_id, result) = match Request::parse(line) {
Ok(request) =>
(
request.request_id,
run(options, request) catch {
error => ShimResult(Failed("\{name} failed: \{error}"))
},
)
Err(problem) =>
match request_id_of(line) {
Some(request_id) =>
(request_id, ShimResult(Failed("malformed request: \{problem}")))
None => raise NoResult("\{name}: malformed request: \{problem}")
}
}
write_result(options.result_file, request_id, result) catch {
error =>
raise NoResult(
"\{name}: cannot write the result to \{options.result_file}: \{error}",
)
}
// For a human watching stderr; the result file already says it.
if result.status is Failed(reason) {
eprintln("\{name}: \{reason}")
}
}