Skip to main content

rig_core/operation/
verify.rs

1//! Status-based credential verification.
2//!
3//! ```
4//! use rig_core::operation::{Verify, VerifyDecoder};
5//! use rig_core::wire::Decoder;
6//!
7//! fn decodes<'id, D: Decoder<'id, Verify>>(_: D) {}
8//! decodes(VerifyDecoder);
9//! ```
10
11use super::Whole;
12use crate::driver::{Model, Transport};
13use crate::error::ProviderError;
14use crate::wire::{Call, Decoder, Flow, Free, Operation, Out, Wire, WireEvent, WireFrame};
15
16/// Checks credentials using response status. HTTP 401/403 indicate invalid
17/// authentication; status-only decoders do not interpret the response body.
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub struct Verify;
20
21impl Operation for Verify {
22    type Request = ();
23    type Event = std::convert::Infallible;
24    type End = ();
25    type Response = ();
26    type Fold = Whole<Self>;
27    type Emit = Free;
28
29    fn fold(_request: &Self::Request, _call: &mut Call<'_>) -> Self::Fold {
30        Whole::new()
31    }
32}
33
34/// Accepts any body, including an empty one, after driver status validation.
35/// Endpoints requiring body validation must use another decoder.
36#[derive(Debug, Default)]
37pub struct VerifyDecoder;
38
39impl<'id> Decoder<'id, Verify> for VerifyDecoder {
40    type Event = ();
41
42    fn classify(&self, _frame: WireFrame) -> WireEvent<Self::Event> {
43        WireEvent::Known(())
44    }
45
46    fn decode(&mut self, _event: (), out: Out<'id, Verify>) -> Result<Flow, ProviderError> {
47        Ok(out.end(()))
48    }
49
50    /// A 2xx with an empty body still verifies: the driver only reaches EOF
51    /// when nothing framed, and the status already said yes.
52    fn eof(&mut self, out: Out<'id, Verify>) -> Result<Flow, ProviderError> {
53        Ok(out.end(()))
54    }
55}
56
57impl<W, T> Model<W, T>
58where
59    W: Wire<Op = Verify>,
60    T: Transport<W>,
61{
62    /// Check that the provider accepts the configured credentials. A 401 or
63    /// 403 reply is [`ProviderError::InvalidAuthentication`].
64    pub async fn verify(&self) -> Result<(), ProviderError> {
65        self.call(()).await.map_err(authentication)
66    }
67}
68
69/// Reclassifies a 401 or 403 reply as [`ProviderError::InvalidAuthentication`],
70/// keeping the reply. Other failures are unchanged.
71fn authentication(error: ProviderError) -> ProviderError {
72    match error {
73        ProviderError::ProviderResponse(response)
74            if matches!(
75                response.status,
76                Some(http::StatusCode::UNAUTHORIZED | http::StatusCode::FORBIDDEN)
77            ) =>
78        {
79            ProviderError::InvalidAuthentication(response)
80        }
81        other => other,
82    }
83}
84
85#[cfg(test)]
86mod tests;