Skip to content

Commit 4f98796

Browse files
committed
feat(webapi): Implement PUT /v1/executions/:id/cancel
1 parent 961b13a commit 4f98796

3 files changed

Lines changed: 134 additions & 39 deletions

File tree

src/command/server.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -692,6 +692,7 @@ async fn run_internal(
692692
let app_router = app_router(WebApiState {
693693
db_pool: server_init.db_pool.clone(),
694694
component_registry_ro: grpc_server.component_registry_ro.clone(),
695+
cancel_registry: grpc_server.cancel_registry.clone(),
695696
});
696697
let app: axum::Router<()> = app_router.fallback_service(grpc_service);
697698
let app_svc = app.into_make_service();

src/server/grpc.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ pub(crate) struct GrpcServer {
5959
pub(crate) component_registry_ro: ComponentConfigRegistryRO,
6060
component_source_map: ComponentSourceMap,
6161
#[debug(skip)]
62-
cancel_registry: CancelRegistry,
62+
pub(crate) cancel_registry: CancelRegistry,
6363
}
6464

6565
impl GrpcServer {

src/server/web_api.rs

Lines changed: 132 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,18 @@ use axum::{
77
use axum_accept::AcceptExtractor;
88
use concepts::{
99
ExecutionId, FinishedExecutionError, FunctionFqn, SupportedFunctionReturnValue,
10-
storage::{DbErrorRead, DbErrorWrite, DbErrorWriteNonRetriable, DbPool, ExecutionEventInner},
10+
storage::{
11+
CancelOutcome, DbErrorRead, DbErrorWrite, DbErrorWriteNonRetriable, DbPool,
12+
ExecutionEventInner,
13+
},
14+
time::{ClockFn as _, Now},
1115
};
1216
use http::StatusCode;
1317
use serde::{Deserialize, Serialize};
1418
use serde_json::json;
1519
use std::sync::Arc;
1620
use val_json::wast_val::WastVal;
21+
use wasm_workers::activity::cancel_registry::CancelRegistry;
1722

1823
use crate::{
1924
command::server::{self, ComponentConfigRegistryRO, SubmitError},
@@ -24,6 +29,7 @@ use crate::{
2429
pub(crate) struct WebApiState {
2530
pub(crate) db_pool: Arc<dyn DbPool>,
2631
pub(crate) component_registry_ro: ComponentConfigRegistryRO,
32+
pub(crate) cancel_registry: CancelRegistry,
2733
}
2834

2935
pub(crate) fn app_router(state: WebApiState) -> Router {
@@ -35,6 +41,10 @@ pub(crate) fn app_router(state: WebApiState) -> Router {
3541
fn v1_router() -> Router<Arc<WebApiState>> {
3642
Router::new()
3743
.route("/execution-id", routing::get(execution_id_generate))
44+
.route(
45+
"/executions/{execution-id}/cancel",
46+
routing::put(execution_cancel),
47+
)
3848
.route(
3949
"/executions/{execution-id}/status",
4050
routing::get(execution_status_get),
@@ -44,28 +54,67 @@ fn v1_router() -> Router<Arc<WebApiState>> {
4454
.route("/components", routing::get(components_list))
4555
}
4656

47-
async fn execution_id_generate(_: State<Arc<WebApiState>>, accept: ExecutionIdAccept) -> Response {
57+
async fn execution_id_generate(_: State<Arc<WebApiState>>, accept: AcceptHeader) -> Response {
4858
let id = ExecutionId::generate();
4959
match accept {
50-
ExecutionIdAccept::Json => Json(json!(id)).into_response(),
51-
ExecutionIdAccept::Text => id.to_string().into_response(),
60+
AcceptHeader::Json => Json(json!(id)).into_response(),
61+
AcceptHeader::Text => id.to_string().into_response(),
62+
}
63+
}
64+
65+
async fn execution_cancel(
66+
Path(execution_id): Path<ExecutionId>,
67+
state: State<Arc<WebApiState>>,
68+
accept: AcceptHeader,
69+
) -> Result<Response, HttpResponse> {
70+
let conn = state.db_pool.connection();
71+
let create_req = conn
72+
.get_create_request(&execution_id)
73+
.await
74+
.map_err(|e| ErrorWrapper(e, accept))?;
75+
// Must verify that this is an activity
76+
if !create_req.component_id.component_type.is_activity() {
77+
return Err(HttpResponse {
78+
status: StatusCode::UNPROCESSABLE_ENTITY,
79+
message: "cancelled execution must be an activity".to_string(),
80+
accept,
81+
});
82+
}
83+
let executed_at = Now.now();
84+
let outcome = state
85+
.cancel_registry
86+
.cancel(conn.as_ref(), &execution_id, executed_at)
87+
.await
88+
.map_err(|e| ErrorWrapper(e, accept))?;
89+
Ok(match outcome {
90+
CancelOutcome::Cancelled => HttpResponse {
91+
status: StatusCode::OK,
92+
message: "cancelled".to_string(),
93+
accept,
94+
},
95+
CancelOutcome::AlreadyFinished => HttpResponse {
96+
status: StatusCode::CONFLICT,
97+
message: "already finished".to_string(),
98+
accept,
99+
},
52100
}
101+
.into_response())
53102
}
54103

55104
async fn execution_status_get(
56105
Path(execution_id): Path<ExecutionId>,
57106
state: State<Arc<WebApiState>>,
58-
accept: ExecutionIdAccept,
59-
) -> Result<Response, ErrorWrapper<DbErrorRead>> {
107+
accept: AcceptHeader,
108+
) -> Result<Response, HttpResponse> {
60109
let pending_state = state
61110
.db_pool
62111
.connection()
63112
.get_pending_state(&execution_id)
64113
.await
65114
.map_err(|e| ErrorWrapper(e, accept))?;
66115
Ok(match accept {
67-
ExecutionIdAccept::Json => Json(json!(pending_state)).into_response(),
68-
ExecutionIdAccept::Text => pending_state.to_string().into_response(),
116+
AcceptHeader::Json => Json(json!(pending_state)).into_response(),
117+
AcceptHeader::Text => pending_state.to_string().into_response(),
69118
})
70119
}
71120

@@ -93,23 +142,24 @@ impl From<SupportedFunctionReturnValue> for RetVal {
93142
async fn execution_get(
94143
Path(execution_id): Path<ExecutionId>,
95144
state: State<Arc<WebApiState>>,
96-
) -> Result<Response, ErrorWrapper<DbErrorRead>> {
145+
) -> Result<Response, HttpResponse> {
97146
let last_event = state
98147
.db_pool
99148
.connection()
100149
.get_last_execution_event(&execution_id)
101150
.await
102-
.map_err(|e| ErrorWrapper(e, ExecutionIdAccept::Json))?;
151+
.map_err(|e| ErrorWrapper(e, AcceptHeader::Json))?;
103152
Ok(
104153
if let ExecutionEventInner::Finished { result, .. } = last_event.event {
105154
let result = RetVal::from(result);
106155
Json(json!(result)).into_response()
107156
} else {
108-
(
109-
StatusCode::TOO_EARLY,
110-
Json(json!({"error":"not finished yet"})),
111-
)
112-
.into_response()
157+
HttpResponse {
158+
status: StatusCode::TOO_EARLY,
159+
message: "not finished yet".to_string(),
160+
accept: AcceptHeader::Json,
161+
}
162+
.into_response()
113163
},
114164
)
115165
}
@@ -134,25 +184,32 @@ async fn execution_submit(
134184
)
135185
.await
136186
{
137-
Ok(()) => (StatusCode::CREATED, Json(json!({ "ok": "created" }))).into_response(),
187+
Ok(()) => HttpResponse {
188+
status: StatusCode::CREATED,
189+
message: "created".to_string(),
190+
accept: AcceptHeader::Json,
191+
}
192+
.into_response(),
138193
Err(SubmitError::DbErrorWrite(DbErrorWrite::NonRetriable(
139194
DbErrorWriteNonRetriable::Conflict,
140-
))) => (
141-
StatusCode::CONFLICT,
142-
Json(json!({ "err": "already exists" })),
143-
)
144-
.into_response(),
145-
Err(err) => (
146-
StatusCode::INTERNAL_SERVER_ERROR,
147-
Json(json!({ "err": err.to_string() })),
148-
)
149-
.into_response(),
195+
))) => HttpResponse {
196+
status: StatusCode::CONFLICT,
197+
message: "already exists".to_string(),
198+
accept: AcceptHeader::Json,
199+
}
200+
.into_response(),
201+
Err(err) => HttpResponse {
202+
status: StatusCode::INTERNAL_SERVER_ERROR,
203+
message: err.to_string(),
204+
accept: AcceptHeader::Json,
205+
}
206+
.into_response(),
150207
}
151208
}
152209

153210
pub(crate) mod components {
154211
use super::{
155-
Arc, Deserialize, ExecutionIdAccept, FunctionFqn, IntoResponse, Json, Query, Response,
212+
AcceptHeader, Arc, Deserialize, FunctionFqn, IntoResponse, Json, Query, Response,
156213
Serialize, State, WebApiState, json,
157214
};
158215
use concepts::{
@@ -174,7 +231,7 @@ pub(crate) mod components {
174231
pub(crate) async fn components_list(
175232
state: State<Arc<WebApiState>>,
176233
Query(params): Query<ComponentsListParams>,
177-
accept: ExecutionIdAccept,
234+
accept: AcceptHeader,
178235
) -> Response {
179236
let extensions = params.extensions.unwrap_or_default();
180237
let mut components = state.component_registry_ro.list(extensions);
@@ -243,8 +300,8 @@ pub(crate) mod components {
243300
.collect();
244301

245302
match accept {
246-
ExecutionIdAccept::Json => Json(json!(components)).into_response(),
247-
ExecutionIdAccept::Text => {
303+
AcceptHeader::Json => Json(json!(components)).into_response(),
304+
AcceptHeader::Text => {
248305
let mut output = String::new();
249306
for component in components {
250307
writeln!(output, "{}", component.component_id).expect("writing to string");
@@ -309,24 +366,61 @@ pub(crate) mod components {
309366
}
310367

311368
#[derive(AcceptExtractor, Clone, Copy)]
312-
pub(crate) enum ExecutionIdAccept {
369+
pub(crate) enum AcceptHeader {
313370
#[accept(mediatype = "text/plain")]
314371
Text,
315372
#[accept(mediatype = "application/json")]
316373
Json,
317374
}
318375

319-
struct ErrorWrapper<E>(E, ExecutionIdAccept);
376+
struct ErrorWrapper<E>(E, AcceptHeader);
377+
378+
struct HttpResponse {
379+
status: StatusCode,
380+
message: String,
381+
accept: AcceptHeader,
382+
}
320383

321-
impl IntoResponse for ErrorWrapper<DbErrorRead> {
384+
impl IntoResponse for HttpResponse {
322385
fn into_response(self) -> Response {
323-
let (status, message) = match self.0 {
386+
match self.accept {
387+
AcceptHeader::Json => (
388+
self.status,
389+
Json(if self.status.is_success() {
390+
json!({ "ok": self.message })
391+
} else {
392+
json!({ "err": self.message })
393+
}),
394+
)
395+
.into_response(),
396+
AcceptHeader::Text => (self.status, self.message).into_response(),
397+
}
398+
}
399+
}
400+
impl From<ErrorWrapper<DbErrorRead>> for HttpResponse {
401+
fn from(value: ErrorWrapper<DbErrorRead>) -> Self {
402+
let (status, message) = match value.0 {
324403
DbErrorRead::NotFound => (StatusCode::NOT_FOUND, "Not found".to_string()),
325-
DbErrorRead::Generic(err) => (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()),
404+
DbErrorRead::Generic(err) => (StatusCode::SERVICE_UNAVAILABLE, err.to_string()),
405+
};
406+
HttpResponse {
407+
status,
408+
message,
409+
accept: value.1,
410+
}
411+
}
412+
}
413+
impl From<ErrorWrapper<DbErrorWrite>> for HttpResponse {
414+
fn from(value: ErrorWrapper<DbErrorWrite>) -> Self {
415+
let (status, message) = match value.0 {
416+
DbErrorWrite::NotFound => (StatusCode::NOT_FOUND, "Not found".to_string()),
417+
DbErrorWrite::Generic(err) => (StatusCode::SERVICE_UNAVAILABLE, err.to_string()),
418+
DbErrorWrite::NonRetriable(err) => (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()),
326419
};
327-
match self.1 {
328-
ExecutionIdAccept::Json => (status, Json(json!({ "error": message }))).into_response(),
329-
ExecutionIdAccept::Text => (status, message).into_response(),
420+
HttpResponse {
421+
status,
422+
message,
423+
accept: value.1,
330424
}
331425
}
332426
}

0 commit comments

Comments
 (0)