1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
//! What one exchange did, as a value.
//!
//! Split from [`crate::exchange`] when diagnostics gave this type a second
//! job. It was a return value the listener discarded; it is now also the
//! word a log line uses for what happened, and [`Outcome::as_str`] is the
//! whole of that second job — the same shape, and for the same reason, as
//! [`PipeStatus::as_str`](crate::PipeStatus::as_str).
//!
//! Nothing here is a secret and nothing here can become one. That is worth
//! stating in a crate where the redaction discipline is otherwise
//! hand-written `Debug` impls: an `Outcome` names a *class* of result and
//! never carries the request that produced it, so putting one in a log
//! field is safe by construction rather than by review.
/// What happened to one exchange.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Outcome {
/// Admitted, and carried to the backend and back.
Forwarded,
/// Refused on the credential. The backend was not contacted.
Unauthorized,
/// Refused on the head — unparseable, oversized, or framed
/// ambiguously. The backend was not contacted.
BadRequest,
/// The peer opened a stream and never finished asking. Nothing was
/// written back, and the backend was not contacted.
TimedOut,
/// The backend was contacted and the exchange failed there — it would
/// not take the connection, or answered with something this edge
/// cannot read. The client was told so; distinct from
/// [`Forwarded`](Self::Forwarded) because nothing came back.
BadGateway,
/// The request body stopped before its declared end — truncated, or
/// framed so this edge could not go on reading it — and the backend,
/// told by a half-close where it stopped, answered nothing this edge
/// could relay.
///
/// Not [`BadRequest`](Self::BadRequest), which promises the backend was
/// never contacted: by the time a body can fail, its head is already
/// upstream. Not [`BadGateway`](Self::BadGateway) either — the backend
/// did nothing wrong, and reporting it there sends whoever is debugging
/// to the far side of a tunnel that was working.
Unfinished,
}
impl Outcome {
/// The name to log this under.
///
/// A borrowed `&'static str` rather than the derived `Debug`, which is
/// what a `tracing` field would otherwise reach for. Two reasons, and
/// the second is the one that matters. It is a value an operator greps
/// for, so it is spelled once here rather than being whatever
/// `#[derive(Debug)]` happens to render — and a `Debug` field on a
/// *different* type is exactly how this crate leaks a credential, so
/// the habit worth having at every log site is naming the string
/// deliberately.
pub(crate) const fn as_str(self) -> &'static str {
match self {
Self::Forwarded => "forwarded",
Self::Unauthorized => "unauthorized",
Self::BadRequest => "bad_request",
Self::TimedOut => "timed_out",
Self::BadGateway => "bad_gateway",
Self::Unfinished => "unfinished",
}
}
}
#[cfg(test)]
#[path = "outcome_tests.rs"]
mod outcome_tests;