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}