Skip to main content

aster_forge_webdav/
event.rs

1//! Observable WebDAV operation events.
2
3use std::time::Duration;
4
5use crate::{DavBackendErrorKind, DavPath, DavRequestHead};
6
7/// Protocol operations exposed to event observers.
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum DavOperation {
10    Options,
11    Propfind,
12    Proppatch,
13    Get,
14    Head,
15    Put,
16    Mkcol,
17    Delete,
18    Copy,
19    Move,
20    Lock,
21    Unlock,
22    Report,
23    VersionControl,
24}
25
26/// Protocol result exposed to observers without credentials, bodies, or lock tokens.
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28pub enum DavEventOutcome {
29    Succeeded {
30        /// HTTP/WebDAV response status without leaking a transport crate version.
31        status: u16,
32    },
33    Failed {
34        /// HTTP/WebDAV response status without leaking a transport crate version.
35        status: u16,
36        backend_error: Option<DavBackendErrorKind>,
37    },
38}
39
40impl DavEventOutcome {
41    /// Classifies a completed response. Informational, success, and redirection statuses are
42    /// successful protocol outcomes; client and server errors are failures.
43    #[must_use]
44    pub const fn from_status(status: u16, backend_error: Option<DavBackendErrorKind>) -> Self {
45        if status < 400 {
46            Self::Succeeded { status }
47        } else {
48            Self::Failed {
49                status,
50                backend_error,
51            }
52        }
53    }
54
55    /// Returns the completed HTTP/WebDAV status.
56    #[must_use]
57    pub const fn status(self) -> u16 {
58        match self {
59            Self::Succeeded { status } | Self::Failed { status, .. } => status,
60        }
61    }
62}
63
64/// One completed WebDAV operation.
65#[derive(Debug, Clone, PartialEq, Eq)]
66pub struct DavEvent {
67    pub request_id: Option<String>,
68    pub operation: DavOperation,
69    pub source: DavPath,
70    pub destination: Option<DavPath>,
71    pub outcome: DavEventOutcome,
72    pub elapsed: Duration,
73}
74
75impl DavEvent {
76    /// Builds the transport-neutral event for one completed request.
77    ///
78    /// Only protocol routing data is copied from the request head. Conditional headers,
79    /// credentials, request bodies, and lock tokens are deliberately excluded.
80    #[must_use]
81    pub fn completed(
82        request_head: &DavRequestHead,
83        status: u16,
84        elapsed: Duration,
85        backend_error: Option<DavBackendErrorKind>,
86    ) -> Self {
87        Self {
88            request_id: None,
89            operation: request_head.method.operation(),
90            source: request_head.target.clone(),
91            destination: request_head
92                .destination
93                .as_ref()
94                .map(|destination| destination.path.clone()),
95            outcome: DavEventOutcome::from_status(status, backend_error),
96            elapsed,
97        }
98    }
99}
100
101/// Non-authoritative observer for audit adapters, metrics, tracing, and notifications.
102///
103/// Required mutations, quota updates, lock persistence, and cache correctness must complete in
104/// the synchronous backend operation before this observer is called.
105pub trait DavEventSink: Send + Sync {
106    fn publish(&self, event: &DavEvent);
107}
108
109/// Event sink used when a product does not need protocol-level observation.
110#[derive(Debug, Clone, Copy, Default)]
111pub struct NoopDavEventSink;
112
113impl DavEventSink for NoopDavEventSink {
114    fn publish(&self, _event: &DavEvent) {}
115}