@@ -366,31 +366,141 @@ impl CocoonServiceImpl {
366366
367367#[ async_trait]
368368impl CocoonService for CocoonServiceImpl {
369- /// Process Mountain requests from Cocoon (generic request-response)
369+ /// Process Mountain requests from Cocoon (generic request-response).
370+ ///
371+ /// Routes legacy `fs.*` / `commands.*` / `secrets.*` method names used by
372+ /// Cocoon's `FileSystemService` and other services that call Mountain via
373+ /// the generic `ProcessCocoonRequest` RPC instead of the typed methods.
374+ ///
375+ /// Parameters are JSON-encoded bytes in `request.parameter`. Results are
376+ /// JSON-encoded bytes in `response.result`.
370377 async fn process_mountain_request (
371378 & self ,
372379 request : Request < GenericRequest > ,
373380 ) -> Result < Response < GenericResponse > , Status > {
374- let request_data = request. into_inner ( ) ;
375- info ! (
376- "[CocoonService] Processing generic Mountain request '{}' with ID {}" ,
377- request_data. method, request_data. request_identifier
378- ) ;
381+ let Req = request. into_inner ( ) ;
382+ let RequestId = Req . request_identifier ;
379383
380- // Request router with method-to-handler mapping
381- // This method provides a generic interface for all CocoonService operations
382- // The actual implementation delegates to specific type-safe methods below
383- warn ! ( "[CocoonService] Generic request router not yet fully implemented - use type-safe methods instead" ) ;
384-
385- Ok ( Response :: new ( GenericResponse {
386- request_identifier : request_data. request_identifier ,
387- result : Vec :: new ( ) ,
388- error : Some ( RpcError {
389- code : -32601 , // Method not found (JSON-RPC error code)
390- message : format ! ( "Method '{}' not implemented in generic router" , request_data. method) ,
391- data : Vec :: new ( ) ,
392- } ) ,
393- } ) )
384+ debug ! ( "[CocoonService] generic request: method={} id={}" , Req . method, RequestId ) ;
385+
386+ /// Serialise a value into the `result` bytes of a GenericResponse.
387+ fn OkResponse ( RequestId : u64 , Value : & impl serde:: Serialize ) -> Response < GenericResponse > {
388+ let Bytes = serde_json:: to_vec ( Value ) . unwrap_or_default ( ) ;
389+ Response :: new ( GenericResponse { request_identifier : RequestId , result : Bytes , error : None } )
390+ }
391+
392+ /// Build an error GenericResponse.
393+ fn ErrResponse ( RequestId : u64 , Code : i32 , Message : String ) -> Response < GenericResponse > {
394+ Response :: new ( GenericResponse {
395+ request_identifier : RequestId ,
396+ result : Vec :: new ( ) ,
397+ error : Some ( RpcError { code : Code , message : Message , data : Vec :: new ( ) } ) ,
398+ } )
399+ }
400+
401+ // Deserialise the generic parameter bytes as a JSON value
402+ let Params : serde_json:: Value = if Req . parameter . is_empty ( ) {
403+ serde_json:: Value :: Null
404+ } else {
405+ serde_json:: from_slice ( & Req . parameter ) . unwrap_or ( serde_json:: Value :: Null )
406+ } ;
407+
408+ match Req . method . as_str ( ) {
409+ // ---- File System ---- (Cocoon FileSystemService uses these paths)
410+ "fs.readFile" | "file:read" => {
411+ let Path = Params . as_str ( ) . or_else ( || Params . get ( "path" ) . and_then ( |V | V . as_str ( ) ) ) . unwrap_or ( "" ) ;
412+ match tokio:: fs:: read ( Path ) . await {
413+ Ok ( Content ) => Ok ( OkResponse ( RequestId , & Content ) ) ,
414+ Err ( Error ) => Ok ( ErrResponse ( RequestId , -32000 , format ! ( "fs.readFile: {}" , Error ) ) ) ,
415+ }
416+ } ,
417+ "fs.writeFile" | "file:write" => {
418+ let Path = Params . get ( "path" ) . and_then ( |V | V . as_str ( ) ) . unwrap_or ( "" ) ;
419+ let Content : Vec < u8 > = Params
420+ . get ( "content" )
421+ . and_then ( |V | V . as_array ( ) )
422+ . map ( |A | A . iter ( ) . filter_map ( |B | B . as_u64 ( ) . map ( |N | N as u8 ) ) . collect ( ) )
423+ . unwrap_or_default ( ) ;
424+ match tokio:: fs:: write ( Path , & Content ) . await {
425+ Ok ( ( ) ) => Ok ( OkResponse ( RequestId , & serde_json:: Value :: Null ) ) ,
426+ Err ( Error ) => Ok ( ErrResponse ( RequestId , -32000 , format ! ( "fs.writeFile: {}" , Error ) ) ) ,
427+ }
428+ } ,
429+ "fs.stat" | "file:stat" => {
430+ let Path = Params . as_str ( ) . or_else ( || Params . get ( "path" ) . and_then ( |V | V . as_str ( ) ) ) . unwrap_or ( "" ) ;
431+ match tokio:: fs:: metadata ( Path ) . await {
432+ Ok ( Meta ) => {
433+ let Mtime = Meta . modified ( ) . ok ( )
434+ . and_then ( |T | T . duration_since ( UNIX_EPOCH ) . ok ( ) )
435+ . map ( |D | D . as_millis ( ) as u64 ) . unwrap_or ( 0 ) ;
436+ Ok ( OkResponse ( RequestId , & json ! ( {
437+ "type" : if Meta . is_dir( ) { 2 } else { 1 } ,
438+ "is_file" : Meta . is_file( ) ,
439+ "is_directory" : Meta . is_dir( ) ,
440+ "size" : Meta . len( ) ,
441+ "mtime" : Mtime ,
442+ } ) ) )
443+ } ,
444+ Err ( Error ) => Ok ( ErrResponse ( RequestId , -32000 , format ! ( "fs.stat: {}" , Error ) ) ) ,
445+ }
446+ } ,
447+ "fs.listDir" | "fs.readdir" | "file:readdir" => {
448+ let Path = Params . as_str ( ) . or_else ( || Params . get ( "path" ) . and_then ( |V | V . as_str ( ) ) ) . unwrap_or ( "" ) ;
449+ match tokio:: fs:: read_dir ( Path ) . await {
450+ Ok ( mut Entries ) => {
451+ let mut Names : Vec < String > = Vec :: new ( ) ;
452+ while let Ok ( Some ( Entry ) ) = Entries . next_entry ( ) . await {
453+ if let Some ( Name ) = Entry . file_name ( ) . to_str ( ) {
454+ Names . push ( Name . to_string ( ) ) ;
455+ }
456+ }
457+ Ok ( OkResponse ( RequestId , & Names ) )
458+ } ,
459+ Err ( Error ) => Ok ( ErrResponse ( RequestId , -32000 , format ! ( "fs.listDir: {}" , Error ) ) ) ,
460+ }
461+ } ,
462+ "fs.createDir" | "file:mkdir" => {
463+ let Path = Params . as_str ( ) . or_else ( || Params . get ( "path" ) . and_then ( |V | V . as_str ( ) ) ) . unwrap_or ( "" ) ;
464+ match tokio:: fs:: create_dir_all ( Path ) . await {
465+ Ok ( ( ) ) => Ok ( OkResponse ( RequestId , & serde_json:: Value :: Null ) ) ,
466+ Err ( Error ) => Ok ( ErrResponse ( RequestId , -32000 , format ! ( "fs.createDir: {}" , Error ) ) ) ,
467+ }
468+ } ,
469+ "fs.delete" | "file:delete" => {
470+ let Path = Params . as_str ( ) . or_else ( || Params . get ( "path" ) . and_then ( |V | V . as_str ( ) ) ) . unwrap_or ( "" ) ;
471+ let Result = if std:: path:: Path :: new ( Path ) . is_dir ( ) {
472+ tokio:: fs:: remove_dir_all ( Path ) . await
473+ } else {
474+ tokio:: fs:: remove_file ( Path ) . await
475+ } ;
476+ match Result {
477+ Ok ( ( ) ) => Ok ( OkResponse ( RequestId , & serde_json:: Value :: Null ) ) ,
478+ Err ( Error ) => Ok ( ErrResponse ( RequestId , -32000 , format ! ( "fs.delete: {}" , Error ) ) ) ,
479+ }
480+ } ,
481+ "fs.rename" | "file:move" => {
482+ let From = Params . get ( "from" ) . and_then ( |V | V . as_str ( ) ) . unwrap_or ( "" ) ;
483+ let To = Params . get ( "to" ) . and_then ( |V | V . as_str ( ) ) . unwrap_or ( "" ) ;
484+ match tokio:: fs:: rename ( From , To ) . await {
485+ Ok ( ( ) ) => Ok ( OkResponse ( RequestId , & serde_json:: Value :: Null ) ) ,
486+ Err ( Error ) => Ok ( ErrResponse ( RequestId , -32000 , format ! ( "fs.rename: {}" , Error ) ) ) ,
487+ }
488+ } ,
489+ // ---- Commands ----
490+ "commands.execute" => {
491+ let CommandId = Params . get ( "id" ) . and_then ( |V | V . as_str ( ) ) . unwrap_or ( "" ) . to_string ( ) ;
492+ let Arg = Params . get ( "arg" ) . cloned ( ) . unwrap_or ( serde_json:: Value :: Null ) ;
493+ match self . environment . ExecuteCommand ( CommandId , Arg ) . await {
494+ Ok ( Value ) => Ok ( OkResponse ( RequestId , & Value ) ) ,
495+ Err ( Error ) => Ok ( ErrResponse ( RequestId , -32000 , Error . to_string ( ) ) ) ,
496+ }
497+ } ,
498+ // ---- Unknown ----
499+ _ => {
500+ warn ! ( "[CocoonService] Unknown generic method: {}" , Req . method) ;
501+ Ok ( ErrResponse ( RequestId , -32601 , format ! ( "Method '{}' not found" , Req . method) ) )
502+ } ,
503+ }
394504 }
395505
396506 /// Send Mountain notifications to Cocoon (generic fire-and-forget)
0 commit comments