wire/reply.rs
1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4// Copyright (c) 2026 2youg1 and the sprawling contributors
5
6//! What goes back to the one who asked: where a refusal is delivered,
7//! where it ended up, and what an outside editor is told about the
8//! request it made.
9//!
10//! Vocabulary, not transport. The city's one writer names these types
11//! and never listens on a socket, so they sit outside the `server`
12//! feature, which carries only the listener (wire D2).
13
14use std::sync::Arc;
15
16use kernel::AxError;
17use serde::Serialize;
18
19/// Where a refusal ended up.
20///
21/// Three states rather than a `Result`, because "nobody asked" and "the
22/// one who asked has gone" are different facts: the first is the
23/// schedule working normally, and the second is worth a diagnostic line.
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25#[must_use]
26pub enum Delivered {
27 ToThePeer,
28 NobodyAsked,
29 PeerGone,
30}
31
32/// Where a refusal goes back to.
33///
34/// A command travels socket to desk to worker thread, and only events
35/// come back, so a refusal made minutes later has no way home. Making
36/// it an event instead would tell everyone watching about one person's
37/// mistyped URL; the city's history is not anybody's error log. So the
38/// command carries the address of whoever sent it.
39///
40/// Holds a function rather than a channel so that this crate's public
41/// signature does not name a transport the assembly layer would then
42/// have to name too.
43#[derive(Clone)]
44pub struct Reply(Option<Arc<dyn Fn(AxError) -> Delivered + Send + Sync>>);
45
46impl Reply {
47 /// A reply address that reaches the peer that sent the command.
48 pub fn to(sink: impl Fn(AxError) -> Delivered + Send + Sync + 'static) -> Reply {
49 Reply(Some(Arc::new(sink)))
50 }
51
52 /// No peer asked. The schedule starts work by itself, and so does
53 /// the startup scan; a refusal there has nobody to be handed to.
54 pub fn nowhere() -> Reply {
55 Reply(None)
56 }
57
58 /// Hands the refusal back, and says where it ended up.
59 pub fn refuse(&self, error: AxError) -> Delivered {
60 match &self.0 {
61 Some(sink) => sink(error),
62 None => Delivered::NobodyAsked,
63 }
64 }
65}
66
67impl std::fmt::Debug for Reply {
68 /// Says whether there is somebody to answer, and never what was
69 /// said: the payload is an `AxError` on its way to one peer.
70 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
71 let face = if self.0.is_some() {
72 "Reply(a peer)"
73 } else {
74 "Reply(nowhere)"
75 };
76 f.write_str(face)
77 }
78}
79
80/// What an accepted request gets back: the run it became, and nothing
81/// else. Progress is what an editor may see; the city's history is not
82/// published through this door.
83#[derive(Debug, Serialize)]
84pub struct AcpProgress {
85 pub run: String,
86 pub turns: u32,
87 pub finished: bool,
88}