Skip to main content

nifi_rust_client/
error.rs

1#![deny(missing_docs)]
2use snafu::Snafu;
3
4/// All errors that can be returned by the NiFi client.
5///
6/// Variants are `#[non_exhaustive]` — new variants may be added in future releases.
7#[derive(Debug, Snafu)]
8#[snafu(visibility(pub(crate)))]
9#[non_exhaustive]
10pub enum NifiError {
11    /// An underlying HTTP transport error from `reqwest`.
12    #[snafu(display("HTTP request failed: {source}"))]
13    Http {
14        /// The underlying reqwest error.
15        source: reqwest::Error,
16    },
17
18    /// The base URL provided to [`NifiClientBuilder`](crate::NifiClientBuilder) could not be parsed.
19    #[snafu(display("Failed to parse NiFi base URL: {source}"))]
20    InvalidBaseUrl {
21        /// The underlying URL parse error.
22        source: url::ParseError,
23    },
24
25    /// Authentication against the NiFi `/access/token` endpoint failed.
26    #[snafu(display("Authentication failed: {message}"))]
27    Auth {
28        /// A description of why authentication failed.
29        message: String,
30    },
31
32    /// A custom CA certificate or client certificate could not be loaded.
33    #[snafu(display("Invalid CA certificate: {source}"))]
34    InvalidCertificate {
35        /// The underlying reqwest error.
36        source: reqwest::Error,
37    },
38
39    /// The NiFi server returned HTTP 401 — credentials are missing or expired.
40    #[snafu(display("Unauthorized (401): {message}"))]
41    Unauthorized {
42        /// The error message returned by NiFi.
43        message: String,
44    },
45
46    /// The NiFi server returned HTTP 403 — the authenticated user lacks permission.
47    #[snafu(display("Forbidden (403): {message}"))]
48    Forbidden {
49        /// The error message returned by NiFi.
50        message: String,
51    },
52
53    /// The NiFi server returned HTTP 404 — the requested resource does not exist.
54    #[snafu(display("Not found (404): {message}"))]
55    NotFound {
56        /// The error message returned by NiFi.
57        message: String,
58    },
59
60    /// The NiFi server returned HTTP 409 — the request conflicts with current state.
61    #[snafu(display("Conflict (409): {message}"))]
62    Conflict {
63        /// The error message returned by NiFi.
64        message: String,
65    },
66
67    /// The NiFi server returned an unexpected non-2xx HTTP status code.
68    #[snafu(display("NiFi API error (status {status}): {message}"))]
69    Api {
70        /// The HTTP status code.
71        status: u16,
72        /// The error message returned by NiFi.
73        message: String,
74    },
75
76    /// The detected NiFi version is not compiled into this client build.
77    ///
78    /// Enable the matching `nifi-x-y-z` feature flag or use the `dynamic` feature.
79    #[snafu(display("NiFi version {detected} is not supported by this client build"))]
80    UnsupportedVersion {
81        /// The version string returned by the NiFi server.
82        detected: String,
83    },
84
85    /// The requested endpoint does not exist in the active NiFi version.
86    ///
87    /// Occurs in dynamic mode when the server version predates a given endpoint.
88    #[snafu(display("Endpoint {endpoint} is not available in NiFi {version}"))]
89    UnsupportedEndpoint {
90        /// The path of the unsupported endpoint.
91        endpoint: String,
92        /// The NiFi version that lacks the endpoint.
93        version: String,
94    },
95
96    /// A response enum field contained a variant not known to this client build.
97    #[snafu(display(
98        "Enum variant '{variant}' of type '{type_name}' is not supported in NiFi {version}"
99    ))]
100    UnsupportedEnumVariant {
101        /// The raw wire value of the unrecognised variant.
102        variant: String,
103        /// The Rust type name of the enum.
104        type_name: String,
105        /// The NiFi version that produced the variant.
106        version: String,
107    },
108
109    /// A required field was absent when converting a dynamic response to a typed struct.
110    #[snafu(display(
111        "Required field '{field}' of type '{type_name}' is missing for NiFi {version}"
112    ))]
113    MissingRequiredField {
114        /// The name of the missing field.
115        field: String,
116        /// The Rust type that expected the field.
117        type_name: String,
118        /// The NiFi version involved.
119        version: String,
120    },
121}
122
123impl NifiError {
124    /// Returns the HTTP status code if this is an API error variant.
125    pub fn status_code(&self) -> Option<u16> {
126        match self {
127            Self::Unauthorized { .. } => Some(401),
128            Self::Forbidden { .. } => Some(403),
129            Self::NotFound { .. } => Some(404),
130            Self::Conflict { .. } => Some(409),
131            Self::Api { status, .. } => Some(*status),
132            _ => None,
133        }
134    }
135
136    /// True if this error is likely transient and worth retrying.
137    pub fn is_retryable(&self) -> bool {
138        matches!(self.status_code(), Some(408 | 429 | 500 | 502 | 503 | 504))
139            || matches!(self, Self::Http { .. })
140    }
141}
142
143/// Create the appropriate typed error from an HTTP status code and message.
144///
145/// Used by all HTTP helpers in `client.rs` to map response status codes
146/// to typed error variants.
147pub(crate) fn api_error(status: u16, message: String) -> NifiError {
148    match status {
149        401 => NifiError::Unauthorized { message },
150        403 => NifiError::Forbidden { message },
151        404 => NifiError::NotFound { message },
152        409 => NifiError::Conflict { message },
153        _ => NifiError::Api { status, message },
154    }
155}