Skip to main content

cow_sdk_core/
validation.rs

1//! Shared validation-failure and transport-classification enums used across
2//! the `cow-sdk` crate family.
3//!
4//! [`ValidationReason`] describes canonical validation-failure modes on the
5//! public input boundary. Downstream crates that surface structured
6//! `{ field, reason: ValidationReason }` error variants route through this
7//! enum so callers can pattern-match on the reason without parsing free-form
8//! strings.
9//!
10//! [`TransportErrorClass`] classifies REST-transport failure categories
11//! produced by `reqwest` error states. Downstream crates that expose typed
12//! `Transport { class, detail }` error variants use this shared enum so
13//! telemetry and retry policies can partition transport outcomes uniformly.
14
15use thiserror::Error;
16
17/// Canonical validation-failure modes emitted by structured error variants.
18///
19/// Variants carry `&'static str` detail slots where applicable so consumers
20/// get actionable context without paying the cost of a heap allocation.
21/// `Missing` names the field itself as the whole reason; structured call
22/// sites still carry a `field: &'static str` alongside this enum to make the
23/// combination self-describing.
24#[non_exhaustive]
25#[derive(Debug, Clone, PartialEq, Eq, Error)]
26pub enum ValidationReason {
27    /// The field was required but was not supplied.
28    #[error("missing required value")]
29    Missing,
30    /// The field was out of the documented acceptable range.
31    #[error("out of range: {details}")]
32    OutOfRange {
33        /// Narrow explanation of the out-of-range condition.
34        details: &'static str,
35    },
36    /// The field did not match the documented shape or format.
37    #[error("bad shape: {details}")]
38    BadShape {
39        /// Narrow explanation of the shape violation.
40        details: &'static str,
41    },
42    /// The field violated a documented precondition for the operation.
43    #[error("precondition violated: {details}")]
44    Precondition {
45        /// Narrow explanation of the violated precondition.
46        details: &'static str,
47    },
48}
49
50/// REST-transport error classification shared across transport-capable crates.
51///
52/// Built by classifying a [`reqwest::Error`] through the documented partition
53/// (`is_timeout`, `is_connect`, `is_redirect`, `is_decode`, `is_body`,
54/// `is_builder`, `is_request`, `is_status`, fallthrough). The
55/// [`ResponseTooLarge`](Self::ResponseTooLarge) class is the exception: it is
56/// produced by the transport's own response-size guard rather than by
57/// `reqwest` classification. Downstream error surfaces pair this enum with a
58/// redacted detail string to produce the public `Transport { class, detail }`
59/// variant shape.
60#[non_exhaustive]
61#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
62pub enum TransportErrorClass {
63    /// The request timed out before a response was received.
64    Timeout,
65    /// The request could not establish a connection to the remote host.
66    Connect,
67    /// The request hit a redirect-handling failure.
68    Redirect,
69    /// Response body decoding failed.
70    Decode,
71    /// Request or response body-stream handling failed.
72    Body,
73    /// The request could not be built locally.
74    Builder,
75    /// The request failed at the HTTP request layer without a structured status.
76    Request,
77    /// The server returned a non-success status code without a structured body.
78    Status,
79    /// HTTP `101 Switching Protocols` upgrade (e.g., WebSocket); reserved
80    /// for future streaming response API. Not currently produced by any
81    /// in-tree transport implementation.
82    Upgrade,
83    /// The response body exceeded the configured maximum size, so the
84    /// transport refused to buffer it. Produced by the SDK's response-size
85    /// guard, not by `reqwest` classification.
86    ResponseTooLarge,
87    /// The transport failure does not match any of the named categories.
88    Other,
89}
90
91impl TransportErrorClass {
92    /// Returns the canonical lowercase label for this class, suitable for
93    /// telemetry labels and structured log fields.
94    #[must_use]
95    pub const fn as_str(self) -> &'static str {
96        match self {
97            Self::Timeout => "timeout",
98            Self::Connect => "connect",
99            Self::Redirect => "redirect",
100            Self::Decode => "decode",
101            Self::Body => "body",
102            Self::Builder => "builder",
103            Self::Request => "request",
104            Self::Status => "status",
105            Self::Upgrade => "upgrade",
106            Self::ResponseTooLarge => "response_too_large",
107            Self::Other => "other",
108        }
109    }
110}
111
112impl std::fmt::Display for TransportErrorClass {
113    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
114        f.write_str(self.as_str())
115    }
116}