@@ -70,6 +70,9 @@ pub enum Channel {
7070}
7171
7272impl Channel {
73+ #[ cfg( test) ]
74+ pub ( crate ) const ALL : [ Self ; 3 ] = [ Self :: Stable , Self :: Beta , Self :: Nightly ] ;
75+
7376 #[ cfg( test) ]
7477 pub ( crate ) fn to_str ( self ) -> & ' static str {
7578 match self {
@@ -358,6 +361,55 @@ pub struct CompileResponse {
358361 pub code : String ,
359362}
360363
364+ #[ derive( Debug , Clone ) ]
365+ pub struct FormatRequest {
366+ pub channel : Channel ,
367+ pub crate_type : CrateType ,
368+ pub edition : Edition ,
369+ pub code : String ,
370+ }
371+
372+ impl FormatRequest {
373+ pub ( crate ) fn delete_previous_main_request ( & self ) -> DeleteFileRequest {
374+ delete_previous_primary_file_request ( self . crate_type )
375+ }
376+
377+ pub ( crate ) fn write_main_request ( & self ) -> WriteFileRequest {
378+ write_primary_file_request ( self . crate_type , & self . code )
379+ }
380+
381+ pub ( crate ) fn execute_cargo_request ( & self ) -> ExecuteCommandRequest {
382+ ExecuteCommandRequest {
383+ cmd : "cargo" . to_owned ( ) ,
384+ args : vec ! [ "fmt" . to_owned( ) ] ,
385+ envs : Default :: default ( ) ,
386+ cwd : None ,
387+ }
388+ }
389+ }
390+
391+ impl CargoTomlModifier for FormatRequest {
392+ fn modify_cargo_toml ( & self , mut cargo_toml : toml:: Value ) -> toml:: Value {
393+ if self . edition == Edition :: Rust2024 {
394+ cargo_toml = modify_cargo_toml:: set_feature_edition2024 ( cargo_toml) ;
395+ }
396+
397+ cargo_toml = modify_cargo_toml:: set_edition ( cargo_toml, self . edition . to_cargo_toml_key ( ) ) ;
398+
399+ if let Some ( crate_type) = self . crate_type . to_library_cargo_toml_key ( ) {
400+ cargo_toml = modify_cargo_toml:: set_crate_type ( cargo_toml, crate_type) ;
401+ }
402+ cargo_toml
403+ }
404+ }
405+
406+ #[ derive( Debug , Clone ) ]
407+ pub struct FormatResponse {
408+ pub success : bool ,
409+ pub exit_detail : String ,
410+ pub code : String ,
411+ }
412+
361413#[ derive( Debug , Clone ) ]
362414pub struct WithOutput < T > {
363415 pub response : T ,
@@ -495,6 +547,33 @@ where
495547 . await
496548 }
497549
550+ pub async fn format (
551+ & self ,
552+ request : FormatRequest ,
553+ ) -> Result < WithOutput < FormatResponse > , FormatError > {
554+ use format_error:: * ;
555+
556+ self . select_channel ( request. channel )
557+ . await
558+ . context ( CouldNotStartContainerSnafu ) ?
559+ . format ( request)
560+ . await
561+ }
562+
563+ pub async fn begin_format (
564+ & self ,
565+ token : CancellationToken ,
566+ request : FormatRequest ,
567+ ) -> Result < ActiveFormatting , FormatError > {
568+ use format_error:: * ;
569+
570+ self . select_channel ( request. channel )
571+ . await
572+ . context ( CouldNotStartContainerSnafu ) ?
573+ . begin_format ( token, request)
574+ . await
575+ }
576+
498577 pub async fn idle ( & mut self ) -> Result < ( ) > {
499578 let Self {
500579 stable,
@@ -767,6 +846,89 @@ impl Container {
767846 } )
768847 }
769848
849+ async fn format (
850+ & self ,
851+ request : FormatRequest ,
852+ ) -> Result < WithOutput < FormatResponse > , FormatError > {
853+ let token = Default :: default ( ) ;
854+
855+ let ActiveFormatting {
856+ task,
857+ stdout_rx,
858+ stderr_rx,
859+ } = self . begin_format ( token, request) . await ?;
860+
861+ WithOutput :: try_absorb ( task, stdout_rx, stderr_rx) . await
862+ }
863+
864+ async fn begin_format (
865+ & self ,
866+ token : CancellationToken ,
867+ request : FormatRequest ,
868+ ) -> Result < ActiveFormatting , FormatError > {
869+ use format_error:: * ;
870+
871+ let delete_previous_main = request. delete_previous_main_request ( ) ;
872+ let write_main = request. write_main_request ( ) ;
873+ let execute_cargo = request. execute_cargo_request ( ) ;
874+ let read_output = ReadFileRequest {
875+ path : request. crate_type . primary_path ( ) . to_owned ( ) ,
876+ } ;
877+
878+ let delete_previous_main = self . commander . one ( delete_previous_main) ;
879+ let write_main = self . commander . one ( write_main) ;
880+ let modify_cargo_toml = self . modify_cargo_toml . modify_for ( & request) ;
881+
882+ let ( delete_previous_main, write_main, modify_cargo_toml) =
883+ join ! ( delete_previous_main, write_main, modify_cargo_toml) ;
884+
885+ delete_previous_main. context ( CouldNotDeletePreviousCodeSnafu ) ?;
886+ write_main. context ( CouldNotWriteCodeSnafu ) ?;
887+ modify_cargo_toml. context ( CouldNotModifyCargoTomlSnafu ) ?;
888+
889+ let SpawnCargo {
890+ task,
891+ stdin_tx,
892+ stdout_rx,
893+ stderr_rx,
894+ } = self
895+ . spawn_cargo_task ( token, execute_cargo)
896+ . await
897+ . context ( CouldNotStartCargoSnafu ) ?;
898+
899+ drop ( stdin_tx) ;
900+
901+ let commander = self . commander . clone ( ) ;
902+ let task = async move {
903+ let ExecuteCommandResponse {
904+ success,
905+ exit_detail,
906+ } = task
907+ . await
908+ . context ( CargoTaskPanickedSnafu ) ?
909+ . context ( CargoFailedSnafu ) ?;
910+
911+ let file = commander
912+ . one ( read_output)
913+ . await
914+ . context ( CouldNotReadCodeSnafu ) ?;
915+ let code = String :: from_utf8 ( file. 0 ) . context ( CodeNotUtf8Snafu ) ?;
916+
917+ Ok ( FormatResponse {
918+ success,
919+ exit_detail,
920+ code,
921+ } )
922+ }
923+ . boxed ( ) ;
924+
925+ Ok ( ActiveFormatting {
926+ task,
927+ stdout_rx,
928+ stderr_rx,
929+ } )
930+ }
931+
770932 async fn spawn_cargo_task (
771933 & self ,
772934 token : CancellationToken ,
@@ -949,6 +1111,53 @@ pub enum CompileError {
9491111 CodeNotUtf8 { source : std:: string:: FromUtf8Error } ,
9501112}
9511113
1114+ pub struct ActiveFormatting {
1115+ pub task : BoxFuture < ' static , Result < FormatResponse , FormatError > > ,
1116+ pub stdout_rx : mpsc:: Receiver < String > ,
1117+ pub stderr_rx : mpsc:: Receiver < String > ,
1118+ }
1119+
1120+ impl fmt:: Debug for ActiveFormatting {
1121+ fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
1122+ f. debug_struct ( "ActiveFormatting" )
1123+ . field ( "task" , & "<future>" )
1124+ . field ( "stdout_rx" , & self . stdout_rx )
1125+ . field ( "stderr_rx" , & self . stderr_rx )
1126+ . finish ( )
1127+ }
1128+ }
1129+
1130+ #[ derive( Debug , Snafu ) ]
1131+ #[ snafu( module) ]
1132+ pub enum FormatError {
1133+ #[ snafu( display( "Could not start the container" ) ) ]
1134+ CouldNotStartContainer { source : Error } ,
1135+
1136+ #[ snafu( display( "Could not modify Cargo.toml" ) ) ]
1137+ CouldNotModifyCargoToml { source : ModifyCargoTomlError } ,
1138+
1139+ #[ snafu( display( "Could not delete previous source code" ) ) ]
1140+ CouldNotDeletePreviousCode { source : CommanderError } ,
1141+
1142+ #[ snafu( display( "Could not write source code" ) ) ]
1143+ CouldNotWriteCode { source : CommanderError } ,
1144+
1145+ #[ snafu( display( "Could not start Cargo task" ) ) ]
1146+ CouldNotStartCargo { source : SpawnCargoError } ,
1147+
1148+ #[ snafu( display( "The Cargo task panicked" ) ) ]
1149+ CargoTaskPanicked { source : tokio:: task:: JoinError } ,
1150+
1151+ #[ snafu( display( "Cargo task failed" ) ) ]
1152+ CargoFailed { source : SpawnCargoError } ,
1153+
1154+ #[ snafu( display( "Could not read the compilation output" ) ) ]
1155+ CouldNotReadCode { source : CommanderError } ,
1156+
1157+ #[ snafu( display( "The compilation output was not UTF-8" ) ) ]
1158+ CodeNotUtf8 { source : std:: string:: FromUtf8Error } ,
1159+ }
1160+
9521161struct SpawnCargo {
9531162 task : JoinHandle < Result < ExecuteCommandResponse , SpawnCargoError > > ,
9541163 stdin_tx : mpsc:: Sender < String > ,
@@ -2282,6 +2491,93 @@ mod tests {
22822491 Ok ( ( ) )
22832492 }
22842493
2494+ const ARBITRARY_FORMAT_REQUEST : FormatRequest = FormatRequest {
2495+ channel : Channel :: Stable ,
2496+ crate_type : CrateType :: Binary ,
2497+ edition : Edition :: Rust2015 ,
2498+ code : String :: new ( ) ,
2499+ } ;
2500+
2501+ const ARBITRARY_FORMAT_INPUT : & str = "fn main(){1+1;}" ;
2502+ #[ rustfmt:: skip]
2503+ const ARBITRARY_FORMAT_OUTPUT : & [ & str ] = & [
2504+ "fn main() {" ,
2505+ " 1 + 1;" ,
2506+ "}"
2507+ ] ;
2508+
2509+ #[ tokio:: test]
2510+ #[ snafu:: report]
2511+ async fn format ( ) -> Result < ( ) > {
2512+ let coordinator = new_coordinator ( ) . await ;
2513+
2514+ let req = FormatRequest {
2515+ code : ARBITRARY_FORMAT_INPUT . into ( ) ,
2516+ ..ARBITRARY_FORMAT_REQUEST
2517+ } ;
2518+
2519+ let response = coordinator. format ( req) . with_timeout ( ) . await . unwrap ( ) ;
2520+
2521+ assert ! ( response. success, "stderr: {}" , response. stderr) ;
2522+ let lines = response. code . lines ( ) . collect :: < Vec < _ > > ( ) ;
2523+ assert_eq ! ( ARBITRARY_FORMAT_OUTPUT , lines) ;
2524+
2525+ Ok ( ( ) )
2526+ }
2527+
2528+ #[ tokio:: test]
2529+ #[ snafu:: report]
2530+ async fn format_channel ( ) -> Result < ( ) > {
2531+ for channel in Channel :: ALL {
2532+ let coordinator = new_coordinator ( ) . await ;
2533+
2534+ let req = FormatRequest {
2535+ channel,
2536+ code : ARBITRARY_FORMAT_INPUT . into ( ) ,
2537+ ..ARBITRARY_FORMAT_REQUEST
2538+ } ;
2539+
2540+ let response = coordinator. format ( req) . with_timeout ( ) . await . unwrap ( ) ;
2541+
2542+ assert ! ( response. success, "stderr: {}" , response. stderr) ;
2543+ let lines = response. code . lines ( ) . collect :: < Vec < _ > > ( ) ;
2544+ assert_eq ! ( ARBITRARY_FORMAT_OUTPUT , lines) ;
2545+ }
2546+
2547+ Ok ( ( ) )
2548+ }
2549+
2550+ #[ tokio:: test]
2551+ #[ snafu:: report]
2552+ async fn format_edition ( ) -> Result < ( ) > {
2553+ let cases = [
2554+ ( "fn main() { async { 1 } }" , [ false , true , true , true ] ) ,
2555+ ( "fn main() { gen { 1 } }" , [ false , false , false , true ] ) ,
2556+ ] ;
2557+
2558+ for ( code, works_in) in cases {
2559+ let coordinator = new_coordinator ( ) . await ;
2560+
2561+ for ( edition, works) in Edition :: ALL . into_iter ( ) . zip ( works_in) {
2562+ let req = FormatRequest {
2563+ edition,
2564+ code : code. into ( ) ,
2565+ channel : Channel :: Nightly , // To allow 2024 while it is unstable
2566+ ..ARBITRARY_FORMAT_REQUEST
2567+ } ;
2568+
2569+ let response = coordinator. format ( req) . with_timeout ( ) . await . unwrap ( ) ;
2570+
2571+ assert_eq ! ( response. success, works, "{code} in {edition:?}" ) ;
2572+ }
2573+ }
2574+
2575+ Ok ( ( ) )
2576+ }
2577+
2578+ // The next set of tests are broader than the functionality of a
2579+ // single operation.
2580+
22852581 #[ tokio:: test]
22862582 #[ snafu:: report]
22872583 async fn compile_clears_old_main_rs ( ) -> Result < ( ) > {
0 commit comments