Skip to main content

early_request_diagnostic/
early_request_diagnostic.rs

1//! OE consumer example, not Facade/Runtime production wiring.
2use saddle_core::{
3    BoundedDiagnosticCause, DiagnosticCategory, DiagnosticCode, DiagnosticOutcomeAxes,
4    DiagnosticStage, OperationOutcome,
5};
6use saddle_observability::{
7    DiagnosticRequestPhase, DiagnosticSubmission, EarlyRequestContext, FrameworkRequestFailure,
8    RequestDiagnosticScope,
9};
10use std::io::{self, Read};
11
12// Keep the full fixed source receipt with the error; no Box on this boundary.
13#[allow(clippy::result_large_err)]
14fn read_head(
15    reader: &mut impl Read,
16    scope: &RequestDiagnosticScope<'_>,
17) -> Result<(), FrameworkRequestFailure<io::Error>> {
18    reader.read_exact(&mut [0; 1]).map_err(|error| {
19        let kind = match error.kind() {
20            io::ErrorKind::UnexpectedEof => "io.unexpected_eof",
21            io::ErrorKind::TimedOut => "io.timed_out",
22            _ => "io.other",
23        };
24        let cause = BoundedDiagnosticCause::new(
25            DiagnosticStage::RequestDecode,
26            DiagnosticCode::new("request.read_failed").unwrap(),
27        )
28        .with_system(DiagnosticCode::new(kind).unwrap(), error.raw_os_error());
29        scope.fail(error, DiagnosticCategory::UnexpectedError, cause)
30    })
31}
32fn main() {
33    // Caller is at the accepted socket phase; no trace, span, request or task is
34    // fabricated to satisfy a constructor. The output may genuinely be absent.
35    let scope =
36        RequestDiagnosticScope::early(None, EarlyRequestContext::socket_accepted("example"))
37            .with_phase(DiagnosticRequestPhase::ReadingHead);
38    let failure = match read_head(&mut io::empty(), &scope) {
39        Err(failure) => failure,
40        Ok(()) => panic!("owned empty reader must fail"),
41    };
42    let id = failure.source_diagnostic().id();
43    let (retained, delivery) = failure.finish_boundary_retained(
44        None,
45        &DiagnosticOutcomeAxes {
46            operation: OperationOutcome::Failed,
47            ..Default::default()
48        },
49    );
50    assert_eq!(retained.error().kind(), io::ErrorKind::UnexpectedEof);
51    assert_eq!(retained.source_diagnostic().id(), id);
52    assert_eq!(
53        delivery.source_submission(),
54        DiagnosticSubmission::OutputUnavailable
55    );
56    assert_eq!(
57        delivery.boundary_submission(),
58        DiagnosticSubmission::OutputUnavailable
59    );
60    // Real consumer keeps `retained` in its task/response result, not just bool.
61    println!("GO: early source retained without invented identities or output");
62}