@@ -7,13 +7,18 @@ use axum::{
77use axum_accept:: AcceptExtractor ;
88use 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} ;
1216use http:: StatusCode ;
1317use serde:: { Deserialize , Serialize } ;
1418use serde_json:: json;
1519use std:: sync:: Arc ;
1620use val_json:: wast_val:: WastVal ;
21+ use wasm_workers:: activity:: cancel_registry:: CancelRegistry ;
1722
1823use crate :: {
1924 command:: server:: { self , ComponentConfigRegistryRO , SubmitError } ,
@@ -24,6 +29,7 @@ use crate::{
2429pub ( 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
2935pub ( crate ) fn app_router ( state : WebApiState ) -> Router {
@@ -35,6 +41,10 @@ pub(crate) fn app_router(state: WebApiState) -> Router {
3541fn 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
55104async 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 {
93142async 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
153210pub ( 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