@@ -142,7 +142,7 @@ pub async fn call<S: ControlStateDelegate + NodeDelegate>(
142142) -> axum:: response:: Result < impl IntoResponse > {
143143 let caller_identity = auth. claims . identity ;
144144
145- let args = parse_call_args ( content_type, body) ?;
145+ let ( args, want_bsatn ) = parse_call_args ( content_type, body) ?;
146146
147147 // HTTP callers always need a connection ID to provide to connect/disconnect,
148148 // so generate one.
@@ -195,8 +195,14 @@ pub async fn call<S: ControlStateDelegate + NodeDelegate>(
195195
196196 match result {
197197 Ok ( CallResult :: Reducer ( result) ) => {
198- let ( status, body) =
199- reducer_outcome_response ( & module, & owner_identity, & reducer, result. outcome , reducer_return_value) ?;
198+ let ( status, body) = reducer_outcome_response (
199+ & module,
200+ & owner_identity,
201+ & reducer,
202+ result. outcome ,
203+ reducer_return_value,
204+ want_bsatn,
205+ ) ?;
200206 Ok ( (
201207 status,
202208 TypedHeader ( SpacetimeEnergyUsed ( result. energy_used ) ) ,
@@ -209,7 +215,7 @@ pub async fn call<S: ControlStateDelegate + NodeDelegate>(
209215 // Procedures don't assign a special meaning to error returns, unlike reducers,
210216 // as there's no transaction for them to automatically abort.
211217 // Instead, we just pass on their return value with the OK status so long as we successfully invoked the procedure.
212- let ( status, body) = procedure_outcome_response ( result. return_val ) ;
218+ let ( status, body) = procedure_outcome_response ( result. return_val , want_bsatn ) ;
213219 Ok ( (
214220 status,
215221 TypedHeader ( SpacetimeExecutionDurationMicros ( result. execution_duration ) ) ,
@@ -225,13 +231,15 @@ pub async fn call<S: ControlStateDelegate + NodeDelegate>(
225231///
226232/// - `application/json` → [`FunctionArgs::Json`] (UTF-8 required).
227233/// - `application/octet-stream` → [`FunctionArgs::Bsatn`] (raw BSATN bytes).
228- fn parse_call_args ( content_type : headers:: ContentType , body : Bytes ) -> axum:: response:: Result < FunctionArgs > {
234+ ///
235+ /// Also returns `want_bsatn`, a bool, which is true if and only if the response should be BSATN-encoded.
236+ fn parse_call_args ( content_type : headers:: ContentType , body : Bytes ) -> axum:: response:: Result < ( FunctionArgs , bool ) > {
229237 if content_type == headers:: ContentType :: json ( ) {
230238 let s = bytestring:: ByteString :: try_from ( body)
231239 . map_err ( |_| ( StatusCode :: BAD_REQUEST , "request body is not valid UTF-8" ) . into_response ( ) ) ?;
232- Ok ( FunctionArgs :: Json ( s) )
240+ Ok ( ( FunctionArgs :: Json ( s) , false ) )
233241 } else if content_type == headers:: ContentType :: octet_stream ( ) {
234- Ok ( FunctionArgs :: Bsatn ( body) )
242+ Ok ( ( FunctionArgs :: Bsatn ( body) , true ) )
235243 } else {
236244 Err ( (
237245 StatusCode :: BAD_REQUEST ,
@@ -276,13 +284,13 @@ pub async fn prepare<S: ControlStateDelegate + NodeDelegate>(
276284 // call_identity_connected/disconnected submit jobs to the module's executor, which
277285 // will be blocked holding the 2PC write lock after prepare_reducer returns — deadlock.
278286 let result = module
279- . prepare_reducer ( caller_identity, None , & reducer, args, coordinator_identity)
287+ . prepare_reducer ( caller_identity, None , & reducer, args. 0 , coordinator_identity)
280288 . await ;
281289
282290 match result {
283291 Ok ( ( prepare_id, rcr, return_value) ) => {
284292 let ( status, body) =
285- reducer_outcome_response ( & module, & owner_identity, & reducer, rcr. outcome , return_value) ?;
293+ reducer_outcome_response ( & module, & owner_identity, & reducer, rcr. outcome , return_value, args . 1 ) ?;
286294 let mut response = (
287295 status,
288296 TypedHeader ( SpacetimeEnergyUsed ( rcr. energy_used ) ) ,
@@ -398,12 +406,18 @@ pub async fn ack_commit_2pc<S: ControlStateDelegate + NodeDelegate>(
398406 Ok ( StatusCode :: OK )
399407}
400408
409+ /// Encode a reducer return value as an HTTP response.
410+ ///
411+ /// If the outcome is an error, return a raw string with `application/text`. Ignore `want_bsatn` in this case.
412+ /// If the outcome is successful, and `want_bsatn`, send BSATN with `application/octet-stream`.
413+ /// If the outcome is successful, and not `want_bsatn`, send JSON with `application/json`.
401414fn reducer_outcome_response (
402415 module : & ModuleHost ,
403416 owner_identity : & Identity ,
404417 reducer : & str ,
405418 outcome : ReducerOutcome ,
406419 reducer_return_value : Option < Bytes > ,
420+ want_bsatn : bool ,
407421) -> axum:: response:: Result < ( StatusCode , axum:: response:: Response ) > {
408422 match outcome {
409423 ReducerOutcome :: Committed => {
@@ -415,20 +429,29 @@ fn reducer_outcome_response(
415429 } ;
416430
417431 if let Some ( bytes) = reducer_return_value. filter ( |value| !value. is_empty ( ) ) {
418- let seed = sats:: WithTypespace :: new ( module. info . module_def . typespace ( ) , & return_value) ;
419- let mut reader = & bytes[ ..] ;
420- let value: AlgebraicValue = seed. deserialize ( bsatn:: Deserializer :: new ( & mut reader) ) . map_err ( |err| {
421- (
422- StatusCode :: INTERNAL_SERVER_ERROR ,
423- format ! ( "Failed to decode reducer return value: {err}" ) ,
424- )
425- } ) ?;
426- Ok ( (
427- StatusCode :: OK ,
428- axum:: Json ( sats:: serde:: SerdeWrapper ( value) ) . into_response ( ) ,
429- ) )
432+ if want_bsatn {
433+ Ok ( ( StatusCode :: OK , bytes. into_response ( ) ) )
434+ } else {
435+ let seed = sats:: WithTypespace :: new ( module. info . module_def . typespace ( ) , & return_value) ;
436+ let mut reader = & bytes[ ..] ;
437+ let value: AlgebraicValue =
438+ seed. deserialize ( bsatn:: Deserializer :: new ( & mut reader) ) . map_err ( |err| {
439+ (
440+ StatusCode :: INTERNAL_SERVER_ERROR ,
441+ format ! ( "Failed to decode reducer return value: {err}" ) ,
442+ )
443+ } ) ?;
444+ Ok ( (
445+ StatusCode :: OK ,
446+ axum:: Json ( sats:: serde:: SerdeWrapper ( value) ) . into_response ( ) ,
447+ ) )
448+ }
430449 } else {
431- Ok ( ( StatusCode :: OK , "" . into_response ( ) ) )
450+ if want_bsatn {
451+ Ok ( ( StatusCode :: OK , Vec :: < u8 > :: new ( ) . into_response ( ) ) )
452+ } else {
453+ Ok ( ( StatusCode :: OK , "" . into_response ( ) ) )
454+ }
432455 }
433456 }
434457 ReducerOutcome :: Failed ( errmsg) => {
@@ -511,10 +534,20 @@ pub enum DBCallErr {
511534 InstanceNotScheduled ,
512535}
513536
514- fn procedure_outcome_response ( return_val : AlgebraicValue ) -> ( StatusCode , axum:: response:: Response ) {
537+ /// Encode a procedure's result as an HTTP response.
538+ ///
539+ /// If `want_bsatn`, send BSATN bytes as `application/octet-stream`.
540+ /// If not `want_bsatn`, send JSON as `application/json`.
541+ fn procedure_outcome_response ( return_val : AlgebraicValue , want_bsatn : bool ) -> ( StatusCode , axum:: response:: Response ) {
515542 (
516543 StatusCode :: OK ,
517- axum:: Json ( sats:: serde:: SerdeWrapper ( return_val) ) . into_response ( ) ,
544+ if want_bsatn {
545+ bsatn:: to_vec ( & return_val)
546+ . expect ( "BSATN serializing an AlgebraicValue should be infallible" )
547+ . into_response ( )
548+ } else {
549+ axum:: Json ( sats:: serde:: SerdeWrapper ( return_val) ) . into_response ( )
550+ } ,
518551 )
519552}
520553
0 commit comments