-
Notifications
You must be signed in to change notification settings - Fork 199
Expand file tree
/
Copy patherror.rs
More file actions
397 lines (346 loc) · 17.8 KB
/
Copy patherror.rs
File metadata and controls
397 lines (346 loc) · 17.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
// Copyright (c) 2023 - 2026 Restate Software, Inc., Restate GmbH.
// All rights reserved.
//
// Use of this software is governed by the Business Source License
// included in the LICENSE file.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0.
use std::ops::RangeInclusive;
use axum::Json;
use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
use codederror::{Code, CodedError};
use restate_core::ShutdownError;
use restate_types::identifiers::{DeploymentId, SubscriptionId};
use restate_types::invocation::ServiceType;
use restate_types::schema::registry::SchemaRegistryError;
use restate_util_string::RestrictedValueError;
use crate::rest_api::ErrorDescriptionResponse;
// --- Few helpers to define Admin API errors.
/// Macro to generate an Admin API Error enum with the given variants.
///
/// All the errors should implement axum IntoResponse and Utoipa's IntoResponses (see macro below).
///
/// Example usage:
///
/// ```rust,ignore
/// generate_meta_api_error!(CancelInvocationError: [InvocationNotFoundError, InvocationClientError, InvalidFieldError, InvocationWasAlreadyCompletedError]);
/// ```
#[macro_export]
macro_rules! generate_meta_api_error {
// Entry point of the macro
($enum_name:ident: [$($variant:ident),* $(,)?]) => {
// Generate the error enum with transparent variants
#[derive(Debug, thiserror::Error)]
#[allow(clippy::enum_variant_names)]
pub enum $enum_name {
$(
#[error(transparent)]
$variant(#[from] $variant),
)*
}
// Generate IntoResponse implementation
impl axum::response::IntoResponse for $enum_name {
fn into_response(self) -> axum::response::Response {
match self {
$(
$enum_name::$variant(err) => err.into_response(),
)*
}
}
}
impl utoipa::IntoResponses for $enum_name {
fn responses() -> std::collections::BTreeMap<String, utoipa::openapi::RefOr<utoipa::openapi::response::Response>> {
let mut result = std::collections::BTreeMap::default();
$(
result.extend(<$variant as utoipa::IntoResponses>::responses());
)*
result
}
}
};
}
// merge_error_responses removed - no longer needed with utoipa
/// Macro to implement both axum IntoResponse and Utoipa's IntoResponses for error types.
/// Error responses are listed explicitly in handler #[utoipa::path] annotations.
///
/// Example usage:
///
/// ```rust,ignore
/// #[derive(Debug, thiserror::Error)]
// #[error("Error message returned in the HTTP API.")]
// pub(crate) struct MyError;
// impl_meta_api_error!(MyError: HTTP_STATUS_CODE "Error description rendered in the OpenAPI.");
/// ```
macro_rules! impl_meta_api_error {
($error_name:ident: $status_code:ident $description:literal) => {
impl IntoResponse for $error_name {
fn into_response(self) -> Response {
(
StatusCode::$status_code,
Json(ErrorDescriptionResponse {
message: self.to_string(),
restate_code: None,
})
).into_response()
}
}
impl utoipa::IntoResponses for $error_name {
fn responses() -> std::collections::BTreeMap<String, utoipa::openapi::RefOr<utoipa::openapi::response::Response>> {
utoipa::openapi::ResponsesBuilder::new()
.response(
StatusCode::$status_code.as_str(),
utoipa::openapi::ResponseBuilder::new()
.description($description)
.content(
"application/json",
utoipa::openapi::ContentBuilder::new()
.schema(Some(<ErrorDescriptionResponse as utoipa::PartialSchema>::schema()))
.build())
.build())
.build()
.into()
}
}
};
($error_name:ident: $status_code:ident) => {
impl_meta_api_error!($error_name: $status_code "");
};
}
// --- Common Admin API errors.
#[derive(Debug, thiserror::Error)]
#[error("The request field '{0}' is invalid. Reason: {1}")]
pub(crate) struct InvalidFieldError(pub(crate) &'static str, pub(crate) String);
impl_meta_api_error!(InvalidFieldError: BAD_REQUEST);
#[derive(Debug, thiserror::Error)]
#[error("The query parameter '{0}' is invalid. Reason: {1}")]
pub(crate) struct InvalidQueryParameterError(pub(crate) &'static str, pub(crate) String);
impl_meta_api_error!(InvalidQueryParameterError: BAD_REQUEST);
#[derive(Debug, thiserror::Error)]
#[error("Batch size {0} exceeds maximum allowed size of {1}")]
pub(crate) struct BatchTooLargeError(pub(crate) usize, pub(crate) usize);
impl_meta_api_error!(BatchTooLargeError: BAD_REQUEST "The batch size exceeds the maximum allowed.");
#[derive(Debug, thiserror::Error)]
#[error("The requested invocation '{0}' does not exist")]
pub(crate) struct InvocationNotFoundError(pub(crate) String);
impl_meta_api_error!(InvocationNotFoundError: NOT_FOUND);
#[derive(Debug, thiserror::Error)]
#[error("Error when routing the request internally. Reason: {0}")]
pub(crate) struct InvocationClientError(
#[from] pub(crate) restate_types::invocation::client::InvocationClientError,
);
impl_meta_api_error!(InvocationClientError: SERVICE_UNAVAILABLE "Error when routing the request within restate.");
#[derive(Debug, thiserror::Error)]
#[error("The invocation '{0}' was already completed.")]
pub(crate) struct InvocationWasAlreadyCompletedError(pub(crate) String);
impl_meta_api_error!(InvocationWasAlreadyCompletedError: CONFLICT "The invocation was already completed, so it cannot be cancelled nor killed. You can instead purge the invocation, in order for restate to forget it.");
#[derive(Debug, thiserror::Error)]
#[error("The invocation '{0}' is not yet completed.")]
pub(crate) struct PurgeInvocationNotCompletedError(pub(crate) String);
impl_meta_api_error!(PurgeInvocationNotCompletedError: CONFLICT "The invocation is not yet completed. An invocation can be purged only when completed.");
#[derive(Debug, thiserror::Error)]
#[error("The invocation '{0}' is still running.")]
pub(crate) struct RestartAsNewInvocationStillRunningError(pub(crate) String);
impl_meta_api_error!(RestartAsNewInvocationStillRunningError: CONFLICT "The invocation is still running. An invocation can be restarted only when completed.");
#[derive(Debug, thiserror::Error)]
#[error(
"Restarting the invocation '{0}' is not supported. Restarting workflows is not supported, and restarting invocations created using the old service protocol."
)]
pub(crate) struct RestartAsNewInvocationUnsupportedError(pub(crate) String);
impl_meta_api_error!(RestartAsNewInvocationUnsupportedError: UNPROCESSABLE_ENTITY "Restarting the invocation is not supported. Restarting workflows is not supported, and restarting invocations created using the old service protocol.");
#[derive(Debug, thiserror::Error)]
#[error(
"The invocation '{0}' cannot be restarted because the input is not available. This indicates that the journal was already purged, or not retained at all."
)]
pub(crate) struct RestartAsNewInvocationMissingInputError(pub(crate) String);
impl_meta_api_error!(RestartAsNewInvocationMissingInputError: GONE "The invocation cannot be restarted because the input is not available. In order to restart an invocation, the journal must be available in order to read the input again. Journal can be retained after completion by enabling journal retention.");
#[derive(Debug, thiserror::Error)]
#[error("The invocation '{0}' cannot be restarted because it's not running yet.")]
pub(crate) struct RestartAsNewInvocationNotStartedError(pub(crate) String);
impl_meta_api_error!(RestartAsNewInvocationNotStartedError: TOO_EARLY "The invocation cannot be restarted because it's not running yet, meaning it might have been scheduled or inboxed.");
#[derive(Debug, thiserror::Error)]
#[error("The invocation '{0}' is completed, cannot be resumed.")]
pub(crate) struct ResumeInvocationCompletedError(pub(crate) String);
impl_meta_api_error!(ResumeInvocationCompletedError: CONFLICT "The invocation is completed. An invocation can be resumed only when running, paused or suspended.");
#[derive(Debug, thiserror::Error)]
#[error("The invocation '{0}' is either inboxed or scheduled, cannot be resumed.")]
pub(crate) struct ResumeInvocationNotStartedError(pub(crate) String);
impl_meta_api_error!(ResumeInvocationNotStartedError: TOO_EARLY "The invocation is either inboxed or scheduled. An invocation can be resumed only when running, paused or suspended.");
#[derive(Debug, thiserror::Error)]
#[error("The invocation '{0}' is not running, cannot be paused.")]
pub(crate) struct PauseInvocationNotRunningError(pub(crate) String);
impl_meta_api_error!(PauseInvocationNotRunningError: CONFLICT "The invocation is not running. An invocation can be paused only when running.");
#[derive(Debug, thiserror::Error)]
#[error(
"The invocation '{0}' is still running or the deployment id is not pinned yet, deployment id cannot be changed."
)]
pub(crate) struct ResumeInvocationCannotChangeDeploymentIdError(pub(crate) String);
impl_meta_api_error!(ResumeInvocationCannotChangeDeploymentIdError: CONFLICT "The invocation is still running or the deployment id is not pinned yet, deployment id cannot be changed. The deployment id can be changed only if the invocation is paused or suspended, and a deployment id is already pinned.");
#[derive(Debug, thiserror::Error)]
#[error("The given deployment was not found when trying to resume the invocation '{0}'.")]
pub(crate) struct ResumeInvocationDeploymentNotFoundError(pub(crate) String);
impl_meta_api_error!(ResumeInvocationDeploymentNotFoundError: BAD_REQUEST "The given deployment was not found.");
#[derive(Debug, thiserror::Error)]
#[error(
"The invocation '{invocation_id}' is running on protocol version '{pinned_protocol_version}', while the chosen deployment '{deployment_id}' supports the range {supported_protocol_versions:?}."
)]
pub(crate) struct ResumeInvocationIncompatibleDeploymentIdError {
pub(crate) invocation_id: String,
pub(crate) pinned_protocol_version: i32,
pub(crate) deployment_id: String,
pub(crate) supported_protocol_versions: RangeInclusive<i32>,
}
impl_meta_api_error!(ResumeInvocationIncompatibleDeploymentIdError: BAD_REQUEST "The selected deployment id to resume the invocation doesn't support the currently pinned service protocol version.");
#[derive(Debug, thiserror::Error)]
#[error("The given index is out of range of currently stored journal for invocation '{0}'.")]
pub(crate) struct RestartAsNewInvocationJournalIndexOutOfRangeError(pub(crate) String);
impl_meta_api_error!(RestartAsNewInvocationJournalIndexOutOfRangeError: BAD_REQUEST "The given journal index is out of range.");
#[derive(Debug, thiserror::Error)]
#[error(
"The prefix of the journal '{0}' up to 'from' (included) contains some Commands without respective Completions."
)]
pub(crate) struct RestartAsNewInvocationJournalCopyRangeInvalidError(pub(crate) String);
impl_meta_api_error!(RestartAsNewInvocationJournalCopyRangeInvalidError: BAD_REQUEST "The given journal prefix contains some Commands without respective Completions.");
#[derive(Debug, thiserror::Error)]
#[error(
"The invocation '{0}' is still running or the deployment id is not pinned yet, deployment id cannot be changed."
)]
pub(crate) struct RestartAsNewInvocationCannotChangeDeploymentIdError(pub(crate) String);
impl_meta_api_error!(RestartAsNewInvocationCannotChangeDeploymentIdError: CONFLICT "The invocation is still running or the deployment id is not pinned yet, deployment id cannot be changed. The deployment id can be changed only if the invocation is paused or suspended, and a deployment id is already pinned.");
#[derive(Debug, thiserror::Error)]
#[error("The given deployment was not found when trying to restart as new the invocation '{0}'.")]
pub(crate) struct RestartAsNewInvocationDeploymentNotFoundError(pub(crate) String);
impl_meta_api_error!(RestartAsNewInvocationDeploymentNotFoundError: BAD_REQUEST "The given deployment was not found.");
#[derive(Debug, thiserror::Error)]
#[error(
"The invocation '{invocation_id}' is running on protocol version '{pinned_protocol_version}', while the chosen deployment '{deployment_id}' supports the range {supported_protocol_versions:?}."
)]
pub(crate) struct RestartAsNewInvocationIncompatibleDeploymentIdError {
pub(crate) invocation_id: String,
pub(crate) pinned_protocol_version: i32,
pub(crate) deployment_id: String,
pub(crate) supported_protocol_versions: RangeInclusive<i32>,
}
impl_meta_api_error!(RestartAsNewInvocationIncompatibleDeploymentIdError: BAD_REQUEST "The selected deployment id to restart as new the invocation doesn't support the currently pinned service protocol version.");
// --- Old Meta API errors. Please don't use these anymore.
/// This error is used by handlers to propagate API errors,
/// and later converted to a response through the IntoResponse implementation
#[derive(Debug, thiserror::Error)]
pub enum MetaApiError {
#[error("The request field '{0}' is invalid. Reason: {1}")]
InvalidField(&'static str, String),
#[error("The requested deployment '{0}' does not exist")]
DeploymentNotFound(DeploymentId),
#[error("The requested service '{0}' does not exist")]
ServiceNotFound(String),
#[error("The requested handler '{handler_name}' on service '{service_name}' does not exist")]
HandlerNotFound {
service_name: String,
handler_name: String,
},
#[error("The requested subscription '{0}' does not exist")]
SubscriptionNotFound(SubscriptionId),
#[error("The requested Kafka cluster '{0}' does not exist")]
KafkaClusterNotFound(String),
#[error("Cannot {0} for service type {1}")]
UnsupportedOperation(&'static str, ServiceType),
#[error(transparent)]
Schema(#[from] SchemaRegistryError),
#[error("Internal server error: {0}")]
Internal(String),
#[error("Conflict: {0}")]
Conflict(String),
#[error("PUT deployment is deprecated, use PATCH instead")]
DeprecatedPutDeployment,
#[error("bad scope: {0}")]
BadScope(RestrictedValueError),
}
impl IntoResponse for MetaApiError {
fn into_response(self) -> Response {
let status_code = match &self {
MetaApiError::ServiceNotFound(_)
| MetaApiError::HandlerNotFound { .. }
| MetaApiError::DeploymentNotFound(_)
| MetaApiError::SubscriptionNotFound(_)
| MetaApiError::KafkaClusterNotFound(_) => StatusCode::NOT_FOUND,
MetaApiError::InvalidField(_, _) | MetaApiError::UnsupportedOperation(_, _) => {
StatusCode::BAD_REQUEST
}
MetaApiError::Schema(error) => error.status_code(),
MetaApiError::Conflict(_) => StatusCode::CONFLICT,
MetaApiError::DeprecatedPutDeployment => StatusCode::METHOD_NOT_ALLOWED,
_ => StatusCode::INTERNAL_SERVER_ERROR,
};
let body = Json(match &self {
MetaApiError::Schema(m) => ErrorDescriptionResponse {
message: m.decorate().to_string(),
restate_code: m.code().map(Code::code),
},
e => ErrorDescriptionResponse {
message: e.to_string(),
restate_code: None,
},
});
(status_code, body).into_response()
}
}
impl utoipa::IntoResponses for MetaApiError {
fn responses()
-> std::collections::BTreeMap<String, utoipa::openapi::RefOr<utoipa::openapi::Response>> {
use std::collections::BTreeMap;
use utoipa::openapi::{Ref, RefOr};
let mut responses = BTreeMap::new();
responses.insert(
"400".to_string(),
RefOr::Ref(Ref::from_response_name("BadRequest")),
);
responses.insert(
"404".to_string(),
RefOr::Ref(Ref::from_response_name("NotFound")),
);
responses.insert(
"405".to_string(),
RefOr::Ref(Ref::from_response_name("MethodNotAllowed")),
);
responses.insert(
"409".to_string(),
RefOr::Ref(Ref::from_response_name("Conflict")),
);
responses.insert(
"500".to_string(),
RefOr::Ref(Ref::from_response_name("InternalServerError")),
);
responses
}
}
pub mod meta_api_error {
//! Those types are only used to generate the corresponding OpenAPI specification for error types
//! that are referenced by [`crate::rest_api::error::MetaApiError`] and [`crate::rest_api::rules::RulesApiError`]
//! when calling [`utoipa::IntoResponses`].
#![allow(dead_code)]
/// Bad request
#[derive(utoipa::ToResponse)]
pub struct BadRequest(super::ErrorDescriptionResponse);
/// Not found
#[derive(utoipa::ToResponse)]
pub struct NotFound(super::ErrorDescriptionResponse);
/// Method not allowed
#[derive(utoipa::ToResponse)]
pub struct MethodNotAllowed(super::ErrorDescriptionResponse);
/// Conflict
#[derive(utoipa::ToResponse)]
pub struct Conflict(super::ErrorDescriptionResponse);
/// Unprocessable entity
#[derive(utoipa::ToResponse)]
pub struct UnprocessableEntity(super::ErrorDescriptionResponse);
/// Internal server error
#[derive(utoipa::ToResponse)]
pub struct InternalServerError(super::ErrorDescriptionResponse);
}
impl From<ShutdownError> for MetaApiError {
fn from(value: ShutdownError) -> Self {
MetaApiError::Internal(value.to_string())
}
}