From a9169a0c1e6b6006ffb0dec5fa8560143dcea709 Mon Sep 17 00:00:00 2001 From: Filip Bozic <70634661+fbozic@users.noreply.github.com> Date: Wed, 30 Oct 2024 10:17:23 +0100 Subject: [PATCH 1/7] feat(meroctl): standardise command output --- crates/meroctl/src/cli.rs | 24 +++++++-- crates/meroctl/src/cli/app.rs | 31 +++++++++-- crates/meroctl/src/cli/app/get.rs | 43 ++++++++++++--- crates/meroctl/src/cli/app/install.rs | 68 +++++++++++++----------- crates/meroctl/src/cli/app/list.rs | 38 +++++++++---- crates/meroctl/src/cli/context.rs | 16 +++--- crates/meroctl/src/cli/context/create.rs | 12 ++--- crates/meroctl/src/cli/context/delete.rs | 6 +-- crates/meroctl/src/cli/context/get.rs | 6 +-- crates/meroctl/src/cli/context/join.rs | 6 +-- crates/meroctl/src/cli/context/list.rs | 6 +-- crates/meroctl/src/cli/context/watch.rs | 8 ++- crates/meroctl/src/cli/jsonrpc.rs | 17 ++---- crates/meroctl/src/common.rs | 8 +++ crates/meroctl/src/main.rs | 1 + crates/meroctl/src/output.rs | 32 +++++++++++ 16 files changed, 221 insertions(+), 101 deletions(-) create mode 100644 crates/meroctl/src/output.rs diff --git a/crates/meroctl/src/cli.rs b/crates/meroctl/src/cli.rs index dacdf8f8d..96a6a2397 100644 --- a/crates/meroctl/src/cli.rs +++ b/crates/meroctl/src/cli.rs @@ -4,6 +4,7 @@ use const_format::concatcp; use eyre::Result as EyreResult; use crate::defaults; +use crate::output::{Format, Output}; mod app; mod context; @@ -54,14 +55,31 @@ pub struct RootArgs { /// Name of node #[arg(short, long, value_name = "NAME")] pub node_name: String, + + #[arg(long, value_name = "FORMAT")] + pub output_format: Format, +} + +pub struct CommandContext { + pub args: RootArgs, + pub output: Output, +} + +impl CommandContext { + pub fn new(args: RootArgs, output: Output) -> Self { + CommandContext { args, output } + } } impl RootCommand { pub async fn run(self) -> EyreResult<()> { + let output = Output::new(self.args.output_format); + let cmd_context = CommandContext::new(self.args, output); + match self.action { - SubCommands::Context(context) => context.run(self.args).await, - SubCommands::App(application) => application.run(self.args).await, - SubCommands::JsonRpc(jsonrpc) => jsonrpc.run(self.args).await, + SubCommands::Context(context) => context.run(cmd_context).await, + SubCommands::App(application) => application.run(cmd_context).await, + SubCommands::JsonRpc(jsonrpc) => jsonrpc.run(cmd_context).await, } } } diff --git a/crates/meroctl/src/cli/app.rs b/crates/meroctl/src/cli/app.rs index 8d8fbe993..ffa5397b6 100644 --- a/crates/meroctl/src/cli/app.rs +++ b/crates/meroctl/src/cli/app.rs @@ -1,11 +1,15 @@ +use std::fmt::Display; + +use calimero_primitives::application::Application; use clap::{Parser, Subcommand}; use const_format::concatcp; use eyre::Result as EyreResult; +use serde::Serialize; -use super::RootArgs; use crate::cli::app::get::GetCommand; use crate::cli::app::install::InstallCommand; use crate::cli::app::list::ListCommand; +use crate::cli::CommandContext; mod get; mod install; @@ -38,12 +42,29 @@ pub enum AppSubCommands { List(ListCommand), } +#[derive(Debug, Serialize)] +pub(crate) struct ApplicationReport(Application); + +impl Display for ApplicationReport { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!(f, "id: {}", self.0.id)?; + writeln!(f, "size: {}", self.0.size)?; + writeln!(f, "blobId: {}", self.0.blob)?; + writeln!(f, "source: {}", self.0.source)?; + writeln!(f, "metadata:")?; + for item in &self.0.metadata { + writeln!(f, " {:?}", item)?; + } + Ok(()) + } +} + impl AppCommand { - pub async fn run(self, args: RootArgs) -> EyreResult<()> { + pub async fn run(self, context: CommandContext) -> EyreResult<()> { match self.subcommand { - AppSubCommands::Get(get) => get.run(args).await, - AppSubCommands::Install(install) => install.run(args).await, - AppSubCommands::List(list) => list.run(args).await, + AppSubCommands::Get(get) => get.run(context).await, + AppSubCommands::Install(install) => install.run(context).await, + AppSubCommands::List(list) => list.run(context).await, } } } diff --git a/crates/meroctl/src/cli/app/get.rs b/crates/meroctl/src/cli/app/get.rs index f4f2dba7d..b27fd5577 100644 --- a/crates/meroctl/src/cli/app/get.rs +++ b/crates/meroctl/src/cli/app/get.rs @@ -1,9 +1,17 @@ -use clap::Parser; +use std::fmt::Display; + +use calimero_server_primitives::admin::GetApplicationResponse; +use clap::{Parser, ValueEnum}; use eyre::{bail, Result as EyreResult}; use reqwest::Client; +use serde::Serialize; -use crate::cli::RootArgs; -use crate::common::{fetch_multiaddr, get_response, load_config, multiaddr_to_url, RequestType}; +use crate::cli::app::ApplicationReport; +use crate::cli::CommandContext; +use crate::common::{ + craft_failed_request_message, fetch_multiaddr, get_response, load_config, multiaddr_to_url, + RequestType, +}; #[derive(Parser, Debug)] #[command(about = "Fetch application details")] @@ -12,10 +20,29 @@ pub struct GetCommand { pub app_id: String, } +#[derive(ValueEnum, Debug, Clone)] +pub enum GetValues { + Details, +} + +#[derive(Debug, Serialize)] +struct OutputReport(GetApplicationResponse); + +impl Display for OutputReport { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self.0.data.application.clone() { + Some(application) => { + write!(f, "{}", ApplicationReport(application)) + } + None => write!(f, "No application found"), + } + } +} + impl GetCommand { #[expect(clippy::print_stdout, reason = "Acceptable for CLI")] - pub async fn run(self, args: RootArgs) -> EyreResult<()> { - let config = load_config(&args.home, &args.node_name)?; + pub async fn run(self, context: CommandContext) -> EyreResult<()> { + let config = load_config(&context.args.home, &context.args.node_name)?; let url = multiaddr_to_url( fetch_multiaddr(&config)?, @@ -32,10 +59,12 @@ impl GetCommand { .await?; if !response.status().is_success() { - bail!("Request failed with status: {}", response.status()) + bail!(craft_failed_request_message(response, "Application get failed").await?) } - println!("{}", response.text().await?); + let response = response.json::().await?; + + context.output.write_output(OutputReport(response)); Ok(()) } diff --git a/crates/meroctl/src/cli/app/install.rs b/crates/meroctl/src/cli/app/install.rs index b095cacba..3b09af5dd 100644 --- a/crates/meroctl/src/cli/app/install.rs +++ b/crates/meroctl/src/cli/app/install.rs @@ -1,3 +1,5 @@ +use std::fmt::Display; + use calimero_primitives::hash::Hash; use calimero_server_primitives::admin::{ InstallApplicationRequest, InstallApplicationResponse, InstallDevApplicationRequest, @@ -6,11 +8,14 @@ use camino::Utf8PathBuf; use clap::Parser; use eyre::{bail, Result}; use reqwest::Client; -use tracing::info; +use serde::Serialize; use url::Url; -use crate::cli::RootArgs; -use crate::common::{fetch_multiaddr, get_response, load_config, multiaddr_to_url, RequestType}; +use crate::cli::CommandContext; +use crate::common::{ + craft_failed_request_message, fetch_multiaddr, get_response, load_config, multiaddr_to_url, + RequestType, +}; #[derive(Debug, Parser)] #[command(about = "Install an application")] @@ -28,26 +33,38 @@ pub struct InstallCommand { pub hash: Option, } +#[derive(Debug, Serialize)] +struct OutputReport(InstallApplicationResponse); + +impl Display for OutputReport { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!(f, "id: {}", self.0.data.application_id) + } +} + impl InstallCommand { - pub async fn run(self, args: RootArgs) -> Result<()> { - let config = load_config(&args.home, &args.node_name)?; + pub async fn run(self, context: CommandContext) -> Result<()> { + let config = load_config(&context.args.home, &context.args.node_name)?; let mut is_dev_installation = false; let metadata = self.metadata.map(String::into_bytes).unwrap_or_default(); - let install_request = if let Some(app_path) = self.path { - let install_dev_request = - InstallDevApplicationRequest::new(app_path.canonicalize_utf8()?, metadata); + let request = if let Some(app_path) = self.path { is_dev_installation = true; - serde_json::to_value(install_dev_request)? + serde_json::to_value(InstallDevApplicationRequest::new( + app_path.canonicalize_utf8()?, + metadata, + ))? } else if let Some(app_url) = self.url { - let install_request = - InstallApplicationRequest::new(Url::parse(&app_url)?, self.hash, metadata); - serde_json::to_value(install_request)? + serde_json::to_value(InstallApplicationRequest::new( + Url::parse(&app_url)?, + self.hash, + metadata, + ))? } else { bail!("Either path or url must be provided"); }; - let install_url = multiaddr_to_url( + let url = multiaddr_to_url( fetch_multiaddr(&config)?, if is_dev_installation { "admin-api/dev/install-dev-application" @@ -56,33 +73,22 @@ impl InstallCommand { }, )?; - let install_response = get_response( + let response = get_response( &Client::new(), - install_url, - Some(install_request), + url, + Some(request), &config.identity, RequestType::Post, ) .await?; - if !install_response.status().is_success() { - let status = install_response.status(); - let error_text = install_response.text().await?; - bail!( - "Application installation failed with status: {}. Error: {}", - status, - error_text - ) + if !response.status().is_success() { + bail!(craft_failed_request_message(response, "Application installation failed").await?) } - let body = install_response - .json::() - .await?; + let response = response.json::().await?; - info!( - "Application installed successfully. Application ID: {}", - body.data.application_id - ); + context.output.write_output(OutputReport(response)); Ok(()) } diff --git a/crates/meroctl/src/cli/app/list.rs b/crates/meroctl/src/cli/app/list.rs index 3e297b03d..9524abdda 100644 --- a/crates/meroctl/src/cli/app/list.rs +++ b/crates/meroctl/src/cli/app/list.rs @@ -1,18 +1,38 @@ +use std::fmt::Display; + use calimero_server_primitives::admin::ListApplicationsResponse; use clap::Parser; use eyre::{bail, Result as EyreResult}; use reqwest::Client; +use serde::Serialize; -use crate::cli::RootArgs; -use crate::common::{fetch_multiaddr, get_response, load_config, multiaddr_to_url, RequestType}; +use crate::cli::app::ApplicationReport; +use crate::cli::CommandContext; +use crate::common::{ + craft_failed_request_message, fetch_multiaddr, get_response, load_config, multiaddr_to_url, + RequestType, +}; #[derive(Debug, Parser)] #[command(about = "List installed applications")] pub struct ListCommand; +#[derive(Debug, Serialize)] +struct OutputReport(ListApplicationsResponse); + +impl Display for OutputReport { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + for app in self.0.data.apps.iter() { + let app_report = ApplicationReport(app.clone()); + write!(f, "{}", app_report)?; + } + Ok(()) + } +} + impl ListCommand { - pub async fn run(self, args: RootArgs) -> EyreResult<()> { - let config = load_config(&args.home, &args.node_name)?; + pub async fn run(self, context: CommandContext) -> EyreResult<()> { + let config = load_config(&context.args.home, &context.args.node_name)?; let response = get_response( &Client::new(), @@ -24,16 +44,12 @@ impl ListCommand { .await?; if !response.status().is_success() { - bail!("Request failed with status: {}", response.status()) + bail!(craft_failed_request_message(response, "Application list failed").await?) } - let api_response: ListApplicationsResponse = response.json().await?; - let app_list = api_response.data.apps; + let response = response.json::().await?; - #[expect(clippy::print_stdout, reason = "Acceptable for CLI")] - for app in app_list { - println!("{}", app.id); - } + context.output.write_output(OutputReport(response)); Ok(()) } diff --git a/crates/meroctl/src/cli/context.rs b/crates/meroctl/src/cli/context.rs index 82b6b1c9d..589fd1f8c 100644 --- a/crates/meroctl/src/cli/context.rs +++ b/crates/meroctl/src/cli/context.rs @@ -8,7 +8,7 @@ use crate::cli::context::get::GetCommand; use crate::cli::context::join::JoinCommand; use crate::cli::context::list::ListCommand; use crate::cli::context::watch::WatchCommand; -use crate::cli::RootArgs; +use crate::cli::CommandContext; mod create; mod delete; @@ -53,14 +53,14 @@ pub enum ContextSubCommands { } impl ContextCommand { - pub async fn run(self, args: RootArgs) -> EyreResult<()> { + pub async fn run(self, context: CommandContext) -> EyreResult<()> { match self.subcommand { - ContextSubCommands::Create(create) => create.run(args).await, - ContextSubCommands::Delete(delete) => delete.run(args).await, - ContextSubCommands::Get(get) => get.run(args).await, - ContextSubCommands::Join(join) => join.run(args).await, - ContextSubCommands::List(list) => list.run(args).await, - ContextSubCommands::Watch(watch) => watch.run(args).await, + ContextSubCommands::Create(create) => create.run(context).await, + ContextSubCommands::Delete(delete) => delete.run(context).await, + ContextSubCommands::Get(get) => get.run(context).await, + ContextSubCommands::Join(join) => join.run(context).await, + ContextSubCommands::List(list) => list.run(context).await, + ContextSubCommands::Watch(watch) => watch.run(context).await, } } } diff --git a/crates/meroctl/src/cli/context/create.rs b/crates/meroctl/src/cli/context/create.rs index e6d88957b..6c555231b 100644 --- a/crates/meroctl/src/cli/context/create.rs +++ b/crates/meroctl/src/cli/context/create.rs @@ -22,7 +22,7 @@ use reqwest::Client; use tokio::runtime::Handle; use tokio::sync::mpsc; -use crate::cli::RootArgs; +use crate::cli::CommandContext; use crate::common::{fetch_multiaddr, get_response, load_config, multiaddr_to_url, RequestType}; #[derive(Debug, Parser)] @@ -65,8 +65,8 @@ pub struct CreateCommand { } impl CreateCommand { - pub async fn run(self, args: RootArgs) -> EyreResult<()> { - let config = load_config(&args.home, &args.node_name)?; + pub async fn run(self, context: CommandContext) -> EyreResult<()> { + let config = load_config(&context.args.home, &context.args.node_name)?; let multiaddr = fetch_multiaddr(&config)?; let client = Client::new(); @@ -100,7 +100,7 @@ impl CreateCommand { let application_id = install_app( &client, - &&multiaddr, + &multiaddr, path.clone(), metadata.clone(), &config.identity, @@ -109,7 +109,7 @@ impl CreateCommand { let context_id = create_context( &client, - &&multiaddr, + &multiaddr, context_seed, application_id, params, @@ -119,7 +119,7 @@ impl CreateCommand { watch_app_and_update_context( &client, - &&multiaddr, + &multiaddr, context_id, path, metadata, diff --git a/crates/meroctl/src/cli/context/delete.rs b/crates/meroctl/src/cli/context/delete.rs index c2aabc48b..06cb5fc7f 100644 --- a/crates/meroctl/src/cli/context/delete.rs +++ b/crates/meroctl/src/cli/context/delete.rs @@ -4,7 +4,7 @@ use libp2p::identity::Keypair; use libp2p::Multiaddr; use reqwest::Client; -use crate::cli::RootArgs; +use crate::cli::CommandContext; use crate::common::{fetch_multiaddr, get_response, load_config, multiaddr_to_url, RequestType}; #[derive(Debug, Parser)] @@ -15,8 +15,8 @@ pub struct DeleteCommand { } impl DeleteCommand { - pub async fn run(self, args: RootArgs) -> EyreResult<()> { - let config = load_config(&args.home, &args.node_name)?; + pub async fn run(self, context: CommandContext) -> EyreResult<()> { + let config = load_config(&context.args.home, &context.args.node_name)?; self.delete_context(fetch_multiaddr(&config)?, &Client::new(), &config.identity) .await diff --git a/crates/meroctl/src/cli/context/get.rs b/crates/meroctl/src/cli/context/get.rs index d868200cf..72997e3a8 100644 --- a/crates/meroctl/src/cli/context/get.rs +++ b/crates/meroctl/src/cli/context/get.rs @@ -4,7 +4,7 @@ use libp2p::identity::Keypair; use libp2p::Multiaddr; use reqwest::Client; -use crate::cli::RootArgs; +use crate::cli::CommandContext; use crate::common::{fetch_multiaddr, get_response, load_config, multiaddr_to_url, RequestType}; #[derive(Parser, Debug)] @@ -26,8 +26,8 @@ pub enum GetRequest { } impl GetCommand { - pub async fn run(self, args: RootArgs) -> EyreResult<()> { - let config = load_config(&args.home, &args.node_name)?; + pub async fn run(self, context: CommandContext) -> EyreResult<()> { + let config = load_config(&context.args.home, &context.args.node_name)?; let multiaddr = fetch_multiaddr(&config)?; let client = Client::new(); diff --git a/crates/meroctl/src/cli/context/join.rs b/crates/meroctl/src/cli/context/join.rs index b4eae1bbc..c946b9034 100644 --- a/crates/meroctl/src/cli/context/join.rs +++ b/crates/meroctl/src/cli/context/join.rs @@ -6,7 +6,7 @@ use eyre::{bail, Result as EyreResult}; use reqwest::Client; use tracing::info; -use crate::cli::RootArgs; +use crate::cli::CommandContext; use crate::common::{fetch_multiaddr, get_response, load_config, multiaddr_to_url, RequestType}; #[derive(Debug, Parser)] @@ -25,8 +25,8 @@ pub struct JoinCommand { } impl JoinCommand { - pub async fn run(self, args: RootArgs) -> EyreResult<()> { - let config = load_config(&args.home, &args.node_name)?; + pub async fn run(self, context: CommandContext) -> EyreResult<()> { + let config = load_config(&context.args.home, &context.args.node_name)?; let response = get_response( &Client::new(), diff --git a/crates/meroctl/src/cli/context/list.rs b/crates/meroctl/src/cli/context/list.rs index e142b3c71..e17292fea 100644 --- a/crates/meroctl/src/cli/context/list.rs +++ b/crates/meroctl/src/cli/context/list.rs @@ -3,7 +3,7 @@ use clap::Parser; use eyre::{bail, Result as EyreResult}; use reqwest::Client; -use crate::cli::RootArgs; +use crate::cli::CommandContext; use crate::common::{fetch_multiaddr, get_response, load_config, multiaddr_to_url, RequestType}; #[derive(Debug, Parser)] @@ -11,8 +11,8 @@ use crate::common::{fetch_multiaddr, get_response, load_config, multiaddr_to_url pub struct ListCommand; impl ListCommand { - pub async fn run(self, args: RootArgs) -> EyreResult<()> { - let config = load_config(&args.home, &args.node_name)?; + pub async fn run(self, context: CommandContext) -> EyreResult<()> { + let config = load_config(&context.args.home, &context.args.node_name)?; let response = get_response( &Client::new(), diff --git a/crates/meroctl/src/cli/context/watch.rs b/crates/meroctl/src/cli/context/watch.rs index bcf6d9b52..50b71eb46 100644 --- a/crates/meroctl/src/cli/context/watch.rs +++ b/crates/meroctl/src/cli/context/watch.rs @@ -5,7 +5,7 @@ use eyre::Result as EyreResult; use futures_util::{SinkExt, StreamExt}; use tokio_tungstenite::connect_async; -use super::RootArgs; +use crate::cli::CommandContext; use crate::common::{fetch_multiaddr, load_config, multiaddr_to_url}; #[derive(Debug, Parser)] @@ -17,10 +17,8 @@ pub struct WatchCommand { } impl WatchCommand { - #[allow(clippy::print_stderr, reason = "TODO")] - #[allow(clippy::print_stdout, reason = "TODO")] - pub async fn run(self, args: RootArgs) -> EyreResult<()> { - let config = load_config(&args.home, &args.node_name)?; + pub async fn run(self, context: CommandContext) -> EyreResult<()> { + let config = load_config(&context.args.home, &context.args.node_name)?; let mut url = multiaddr_to_url(fetch_multiaddr(&config)?, "ws")?; url.set_scheme("ws") diff --git a/crates/meroctl/src/cli/jsonrpc.rs b/crates/meroctl/src/cli/jsonrpc.rs index adb3b114c..be9ace557 100644 --- a/crates/meroctl/src/cli/jsonrpc.rs +++ b/crates/meroctl/src/cli/jsonrpc.rs @@ -1,4 +1,3 @@ -use calimero_config::ConfigFile; use calimero_primitives::context::ContextId; use calimero_server_primitives::jsonrpc::{ ExecuteRequest, Request, RequestId, RequestPayload, Version, @@ -8,8 +7,8 @@ use const_format::concatcp; use eyre::{bail, Result as EyreResult}; use serde_json::Value; -use super::RootArgs; -use crate::common::{get_response, multiaddr_to_url, RequestType}; +use crate::cli::CommandContext; +use crate::common::{get_response, load_config, multiaddr_to_url, RequestType}; pub const EXAMPLES: &str = r" # Execute a RPC method call @@ -51,16 +50,8 @@ pub enum CallType { #[expect(clippy::print_stdout, reason = "Acceptable for CLI")] impl CallCommand { - pub async fn run(self, root_args: RootArgs) -> EyreResult<()> { - let path = root_args.home.join(&root_args.node_name); - - if !ConfigFile::exists(&path) { - bail!("Config file does not exist") - }; - - let Ok(config) = ConfigFile::load(&path) else { - bail!("Failed to load config file") - }; + pub async fn run(self, context: CommandContext) -> EyreResult<()> { + let config = load_config(&context.args.home, &context.args.node_name)?; let Some(multiaddr) = config.network.server.listen.first() else { bail!("No address.") diff --git a/crates/meroctl/src/common.rs b/crates/meroctl/src/common.rs index 1b0015dcd..b5c7babd9 100644 --- a/crates/meroctl/src/common.rs +++ b/crates/meroctl/src/common.rs @@ -88,3 +88,11 @@ pub enum RequestType { Post, Delete, } +pub async fn craft_failed_request_message(response: Response, message: &str) -> EyreResult { + let status = response.status(); + let error_text = response.text().await?; + Ok(format!( + "{} - Status: {}, Error: {}", + message, status, error_text + )) +} diff --git a/crates/meroctl/src/main.rs b/crates/meroctl/src/main.rs index 051e2c59a..5d9021b00 100644 --- a/crates/meroctl/src/main.rs +++ b/crates/meroctl/src/main.rs @@ -12,6 +12,7 @@ use crate::cli::RootCommand; mod cli; mod common; mod defaults; +mod output; #[tokio::main] async fn main() -> EyreResult<()> { diff --git a/crates/meroctl/src/output.rs b/crates/meroctl/src/output.rs new file mode 100644 index 000000000..d18232e2c --- /dev/null +++ b/crates/meroctl/src/output.rs @@ -0,0 +1,32 @@ +use clap::ValueEnum; +use serde::Serialize; + +#[derive(Clone, Copy, Debug, Default, ValueEnum, Serialize)] +pub enum Format { + Json, + #[default] + PlainText, +} + +#[derive(Debug, Default)] +pub struct Output { + format: Format, +} + +impl Output { + pub fn new(output_type: Format) -> Self { + Output { + format: output_type, + } + } + + pub fn write_output(&self, value: T) { + match self.format { + Format::Json => match serde_json::to_string(&value) { + Ok(json) => println!("{}", json), + Err(e) => eprintln!("Failed to serialize to JSON: {}", e), + }, + Format::PlainText => println!("{}", value), + } + } +} From 6797b3bb6f127b5277850f358764851cc8a6e47a Mon Sep 17 00:00:00 2001 From: Filip Bozic <70634661+fbozic@users.noreply.github.com> Date: Wed, 30 Oct 2024 10:32:50 +0100 Subject: [PATCH 2/7] feat(meroctl): use lifetime generics for ApplicationReport --- crates/meroctl/src/cli/app.rs | 4 ++-- crates/meroctl/src/cli/app/get.rs | 4 ++-- crates/meroctl/src/cli/app/list.rs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/meroctl/src/cli/app.rs b/crates/meroctl/src/cli/app.rs index ffa5397b6..bb7dd0c54 100644 --- a/crates/meroctl/src/cli/app.rs +++ b/crates/meroctl/src/cli/app.rs @@ -43,9 +43,9 @@ pub enum AppSubCommands { } #[derive(Debug, Serialize)] -pub(crate) struct ApplicationReport(Application); +pub(crate) struct ApplicationReport<'a>(&'a Application); -impl Display for ApplicationReport { +impl Display for ApplicationReport<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { writeln!(f, "id: {}", self.0.id)?; writeln!(f, "size: {}", self.0.size)?; diff --git a/crates/meroctl/src/cli/app/get.rs b/crates/meroctl/src/cli/app/get.rs index b27fd5577..19980531a 100644 --- a/crates/meroctl/src/cli/app/get.rs +++ b/crates/meroctl/src/cli/app/get.rs @@ -30,8 +30,8 @@ struct OutputReport(GetApplicationResponse); impl Display for OutputReport { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self.0.data.application.clone() { - Some(application) => { + match self.0.data.application { + Some(ref application) => { write!(f, "{}", ApplicationReport(application)) } None => write!(f, "No application found"), diff --git a/crates/meroctl/src/cli/app/list.rs b/crates/meroctl/src/cli/app/list.rs index 9524abdda..1acde7147 100644 --- a/crates/meroctl/src/cli/app/list.rs +++ b/crates/meroctl/src/cli/app/list.rs @@ -23,7 +23,7 @@ struct OutputReport(ListApplicationsResponse); impl Display for OutputReport { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { for app in self.0.data.apps.iter() { - let app_report = ApplicationReport(app.clone()); + let app_report = ApplicationReport(&app); write!(f, "{}", app_report)?; } Ok(()) From 5eb6e1b06a7875fbbac2101a6b8fddd0710b0678 Mon Sep 17 00:00:00 2001 From: Filip Bozic <70634661+fbozic@users.noreply.github.com> Date: Wed, 30 Oct 2024 21:44:07 +0100 Subject: [PATCH 3/7] feat(meroctl): improve error handling, rename context to environment --- Cargo.lock | 3 +- Cargo.toml | 4 +- crates/meroctl/Cargo.toml | 3 +- crates/meroctl/src/cli.rs | 78 ++- crates/meroctl/src/cli/app.rs | 36 +- crates/meroctl/src/cli/app/get.rs | 44 +- crates/meroctl/src/cli/app/install.rs | 34 +- crates/meroctl/src/cli/app/list.rs | 41 +- crates/meroctl/src/cli/context.rs | 26 +- crates/meroctl/src/cli/context/create.rs | 121 ++--- crates/meroctl/src/cli/context/delete.rs | 49 +- crates/meroctl/src/cli/context/get.rs | 110 ++-- crates/meroctl/src/cli/context/join.rs | 39 +- crates/meroctl/src/cli/context/list.rs | 33 +- crates/meroctl/src/cli/context/watch.rs | 6 +- crates/meroctl/src/cli/jsonrpc.rs | 23 +- crates/meroctl/src/common.rs | 87 ++- crates/meroctl/src/main.rs | 27 +- crates/meroctl/src/output.rs | 8 +- crates/server-primitives/src/admin.rs | 509 ++++++++++-------- crates/server/src/admin/handlers/context.rs | 2 +- .../admin/handlers/context/delete_context.rs | 15 +- .../src/admin/handlers/context/get_context.rs | 31 +- .../context/get_context_client_keys.rs | 18 +- .../context/get_context_identities.rs | 18 +- .../handlers/context/get_context_storage.rs | 17 +- .../handlers/context/get_context_users.rs | 16 +- ...on_id.rs => update_context_application.rs} | 14 +- crates/server/src/admin/service.rs | 4 +- 29 files changed, 744 insertions(+), 672 deletions(-) rename crates/server/src/admin/handlers/context/{update_application_id.rs => update_context_application.rs} (79%) diff --git a/Cargo.lock b/Cargo.lock index 6ad058fa2..f416f4ed8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4472,10 +4472,9 @@ dependencies = [ "reqwest 0.12.5", "serde", "serde_json", + "thiserror", "tokio", "tokio-tungstenite 0.24.0", - "tracing", - "tracing-subscriber", "url", ] diff --git a/Cargo.toml b/Cargo.toml index cd1b12dbe..850b1892b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -237,8 +237,8 @@ lossy_float_literal = "deny" mem_forget = "deny" multiple_inherent_impl = "deny" #panic = "deny" TODO: Enable as soon as possible -print_stderr = "deny" -print_stdout = "deny" +print_stderr = "warn" +print_stdout = "warn" rc_mutex = "deny" renamed_function_params = "deny" try_err = "deny" diff --git a/crates/meroctl/Cargo.toml b/crates/meroctl/Cargo.toml index 110150fc3..6619d5ea0 100644 --- a/crates/meroctl/Cargo.toml +++ b/crates/meroctl/Cargo.toml @@ -23,10 +23,9 @@ notify.workspace = true reqwest = { workspace = true, features = ["json"] } serde = { workspace = true, features = ["derive"] } serde_json.workspace = true +thiserror.workspace = true tokio = { workspace = true, features = ["io-std", "macros"] } tokio-tungstenite.workspace = true -tracing.workspace = true -tracing-subscriber = { workspace = true, features = ["env-filter"] } url = { workspace = true, features = ["serde"] } calimero-config = { path = "../config" } diff --git a/crates/meroctl/src/cli.rs b/crates/meroctl/src/cli.rs index 96a6a2397..8d7a97216 100644 --- a/crates/meroctl/src/cli.rs +++ b/crates/meroctl/src/cli.rs @@ -1,10 +1,14 @@ +use std::process::ExitCode; + use camino::Utf8PathBuf; use clap::{Parser, Subcommand}; use const_format::concatcp; -use eyre::Result as EyreResult; +use eyre::Report as EyreReport; +use serde::{Serialize, Serializer}; +use thiserror::Error as ThisError; use crate::defaults; -use crate::output::{Format, Output}; +use crate::output::{Format, Output, Report}; mod app; mod context; @@ -60,26 +64,78 @@ pub struct RootArgs { pub output_format: Format, } -pub struct CommandContext { +pub struct Environment { pub args: RootArgs, pub output: Output, } -impl CommandContext { +impl Environment { pub fn new(args: RootArgs, output: Output) -> Self { - CommandContext { args, output } + Environment { args, output } } } impl RootCommand { - pub async fn run(self) -> EyreResult<()> { + pub async fn run(self) -> Result<(), CliError> { let output = Output::new(self.args.output_format); - let cmd_context = CommandContext::new(self.args, output); + let environment = Environment::new(self.args, output); + + let result = match self.action { + SubCommands::Context(context) => context.run(&environment).await, + SubCommands::App(application) => application.run(&environment).await, + SubCommands::JsonRpc(jsonrpc) => jsonrpc.run(&environment).await, + }; - match self.action { - SubCommands::Context(context) => context.run(cmd_context).await, - SubCommands::App(application) => application.run(cmd_context).await, - SubCommands::JsonRpc(jsonrpc) => jsonrpc.run(cmd_context).await, + if let Err(err) = result { + let err = match err.downcast::() { + Ok(err) => CliError::ApiError(err), + Err(err) => CliError::Other(err), + }; + environment.output.write(&err); } + + return Ok(()); } } + +#[derive(Debug, Serialize, ThisError)] +pub enum CliError { + #[error(transparent)] + ApiError(#[from] ApiError), + + #[error(transparent)] + Other( + #[from] + #[serde(serialize_with = "serialize_eyre_report")] + EyreReport, + ), +} + +impl Into for CliError { + fn into(self) -> ExitCode { + match self { + CliError::ApiError(_) => ExitCode::from(101), + CliError::Other(_) => ExitCode::FAILURE, + } + } +} + +impl Report for CliError { + fn report(&self) { + println!("{}", self); + } +} + +#[derive(Debug, Serialize, ThisError)] +#[error("ApiError")] +pub struct ApiError { + pub status_code: u16, + pub message: String, +} + +fn serialize_eyre_report(report: &EyreReport, serializer: S) -> Result +where + S: Serializer, +{ + serializer.serialize_str(&report.to_string()) +} diff --git a/crates/meroctl/src/cli/app.rs b/crates/meroctl/src/cli/app.rs index bb7dd0c54..d3657f4e8 100644 --- a/crates/meroctl/src/cli/app.rs +++ b/crates/meroctl/src/cli/app.rs @@ -1,15 +1,13 @@ -use std::fmt::Display; - use calimero_primitives::application::Application; use clap::{Parser, Subcommand}; use const_format::concatcp; use eyre::Result as EyreResult; -use serde::Serialize; use crate::cli::app::get::GetCommand; use crate::cli::app::install::InstallCommand; use crate::cli::app::list::ListCommand; -use crate::cli::CommandContext; +use crate::cli::Environment; +use crate::output::Report; mod get; mod install; @@ -42,29 +40,25 @@ pub enum AppSubCommands { List(ListCommand), } -#[derive(Debug, Serialize)] -pub(crate) struct ApplicationReport<'a>(&'a Application); - -impl Display for ApplicationReport<'_> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - writeln!(f, "id: {}", self.0.id)?; - writeln!(f, "size: {}", self.0.size)?; - writeln!(f, "blobId: {}", self.0.blob)?; - writeln!(f, "source: {}", self.0.source)?; - writeln!(f, "metadata:")?; - for item in &self.0.metadata { - writeln!(f, " {:?}", item)?; +impl Report for Application { + fn report(&self) { + println!("id: {}", self.id); + println!("size: {}", self.size); + println!("blobId: {}", self.blob); + println!("source: {}", self.source); + println!("metadata:"); + for item in &self.metadata { + println!(" {:?}", item); } - Ok(()) } } impl AppCommand { - pub async fn run(self, context: CommandContext) -> EyreResult<()> { + pub async fn run(self, environment: &Environment) -> EyreResult<()> { match self.subcommand { - AppSubCommands::Get(get) => get.run(context).await, - AppSubCommands::Install(install) => install.run(context).await, - AppSubCommands::List(list) => list.run(context).await, + AppSubCommands::Get(get) => get.run(environment).await, + AppSubCommands::Install(install) => install.run(environment).await, + AppSubCommands::List(list) => list.run(environment).await, } } } diff --git a/crates/meroctl/src/cli/app/get.rs b/crates/meroctl/src/cli/app/get.rs index 19980531a..9c6eb9c80 100644 --- a/crates/meroctl/src/cli/app/get.rs +++ b/crates/meroctl/src/cli/app/get.rs @@ -1,17 +1,11 @@ -use std::fmt::Display; - use calimero_server_primitives::admin::GetApplicationResponse; use clap::{Parser, ValueEnum}; -use eyre::{bail, Result as EyreResult}; +use eyre::Result as EyreResult; use reqwest::Client; -use serde::Serialize; -use crate::cli::app::ApplicationReport; -use crate::cli::CommandContext; -use crate::common::{ - craft_failed_request_message, fetch_multiaddr, get_response, load_config, multiaddr_to_url, - RequestType, -}; +use crate::cli::Environment; +use crate::common::{do_request, fetch_multiaddr, load_config, multiaddr_to_url, RequestType}; +use crate::output::Report; #[derive(Parser, Debug)] #[command(about = "Fetch application details")] @@ -25,31 +19,25 @@ pub enum GetValues { Details, } -#[derive(Debug, Serialize)] -struct OutputReport(GetApplicationResponse); - -impl Display for OutputReport { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self.0.data.application { - Some(ref application) => { - write!(f, "{}", ApplicationReport(application)) - } - None => write!(f, "No application found"), +impl Report for GetApplicationResponse { + fn report(&self) { + match self.data.application { + Some(ref application) => application.report(), + None => println!("No application found"), } } } impl GetCommand { - #[expect(clippy::print_stdout, reason = "Acceptable for CLI")] - pub async fn run(self, context: CommandContext) -> EyreResult<()> { - let config = load_config(&context.args.home, &context.args.node_name)?; + pub async fn run(self, environment: &Environment) -> EyreResult<()> { + let config = load_config(&environment.args.home, &environment.args.node_name)?; let url = multiaddr_to_url( fetch_multiaddr(&config)?, &format!("admin-api/dev/applications/{}", self.app_id), )?; - let response = get_response( + let response: GetApplicationResponse = do_request( &Client::new(), url, None::<()>, @@ -58,13 +46,7 @@ impl GetCommand { ) .await?; - if !response.status().is_success() { - bail!(craft_failed_request_message(response, "Application get failed").await?) - } - - let response = response.json::().await?; - - context.output.write_output(OutputReport(response)); + environment.output.write(&response); Ok(()) } diff --git a/crates/meroctl/src/cli/app/install.rs b/crates/meroctl/src/cli/app/install.rs index 3b09af5dd..c8487f415 100644 --- a/crates/meroctl/src/cli/app/install.rs +++ b/crates/meroctl/src/cli/app/install.rs @@ -1,5 +1,3 @@ -use std::fmt::Display; - use calimero_primitives::hash::Hash; use calimero_server_primitives::admin::{ InstallApplicationRequest, InstallApplicationResponse, InstallDevApplicationRequest, @@ -8,14 +6,11 @@ use camino::Utf8PathBuf; use clap::Parser; use eyre::{bail, Result}; use reqwest::Client; -use serde::Serialize; use url::Url; -use crate::cli::CommandContext; -use crate::common::{ - craft_failed_request_message, fetch_multiaddr, get_response, load_config, multiaddr_to_url, - RequestType, -}; +use crate::cli::Environment; +use crate::common::{do_request, fetch_multiaddr, load_config, multiaddr_to_url, RequestType}; +use crate::output::Report; #[derive(Debug, Parser)] #[command(about = "Install an application")] @@ -33,18 +28,15 @@ pub struct InstallCommand { pub hash: Option, } -#[derive(Debug, Serialize)] -struct OutputReport(InstallApplicationResponse); - -impl Display for OutputReport { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - writeln!(f, "id: {}", self.0.data.application_id) +impl Report for InstallApplicationResponse { + fn report(&self) { + println!("id: {}", self.data.application_id); } } impl InstallCommand { - pub async fn run(self, context: CommandContext) -> Result<()> { - let config = load_config(&context.args.home, &context.args.node_name)?; + pub async fn run(self, environment: &Environment) -> Result<()> { + let config = load_config(&environment.args.home, &environment.args.node_name)?; let mut is_dev_installation = false; let metadata = self.metadata.map(String::into_bytes).unwrap_or_default(); @@ -73,7 +65,7 @@ impl InstallCommand { }, )?; - let response = get_response( + let response: InstallApplicationResponse = do_request( &Client::new(), url, Some(request), @@ -82,13 +74,7 @@ impl InstallCommand { ) .await?; - if !response.status().is_success() { - bail!(craft_failed_request_message(response, "Application installation failed").await?) - } - - let response = response.json::().await?; - - context.output.write_output(OutputReport(response)); + environment.output.write(&response); Ok(()) } diff --git a/crates/meroctl/src/cli/app/list.rs b/crates/meroctl/src/cli/app/list.rs index 1acde7147..7bd35e760 100644 --- a/crates/meroctl/src/cli/app/list.rs +++ b/crates/meroctl/src/cli/app/list.rs @@ -1,40 +1,29 @@ -use std::fmt::Display; - use calimero_server_primitives::admin::ListApplicationsResponse; use clap::Parser; -use eyre::{bail, Result as EyreResult}; +use eyre::Result as EyreResult; use reqwest::Client; -use serde::Serialize; -use crate::cli::app::ApplicationReport; -use crate::cli::CommandContext; -use crate::common::{ - craft_failed_request_message, fetch_multiaddr, get_response, load_config, multiaddr_to_url, - RequestType, -}; +use crate::cli::Environment; +use crate::common::{do_request, fetch_multiaddr, load_config, multiaddr_to_url, RequestType}; +use crate::output::Report; #[derive(Debug, Parser)] #[command(about = "List installed applications")] pub struct ListCommand; -#[derive(Debug, Serialize)] -struct OutputReport(ListApplicationsResponse); - -impl Display for OutputReport { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - for app in self.0.data.apps.iter() { - let app_report = ApplicationReport(&app); - write!(f, "{}", app_report)?; +impl Report for ListApplicationsResponse { + fn report(&self) { + for application in self.data.apps.iter() { + application.report(); } - Ok(()) } } impl ListCommand { - pub async fn run(self, context: CommandContext) -> EyreResult<()> { - let config = load_config(&context.args.home, &context.args.node_name)?; + pub async fn run(self, environment: &Environment) -> EyreResult<()> { + let config = load_config(&environment.args.home, &environment.args.node_name)?; - let response = get_response( + let response: ListApplicationsResponse = do_request( &Client::new(), multiaddr_to_url(fetch_multiaddr(&config)?, "admin-api/dev/applications")?, None::<()>, @@ -43,13 +32,7 @@ impl ListCommand { ) .await?; - if !response.status().is_success() { - bail!(craft_failed_request_message(response, "Application list failed").await?) - } - - let response = response.json::().await?; - - context.output.write_output(OutputReport(response)); + environment.output.write(&response); Ok(()) } diff --git a/crates/meroctl/src/cli/context.rs b/crates/meroctl/src/cli/context.rs index 589fd1f8c..75dfa402a 100644 --- a/crates/meroctl/src/cli/context.rs +++ b/crates/meroctl/src/cli/context.rs @@ -1,3 +1,4 @@ +use calimero_primitives::context::Context; use clap::{Parser, Subcommand}; use const_format::concatcp; use eyre::Result as EyreResult; @@ -8,7 +9,8 @@ use crate::cli::context::get::GetCommand; use crate::cli::context::join::JoinCommand; use crate::cli::context::list::ListCommand; use crate::cli::context::watch::WatchCommand; -use crate::cli::CommandContext; +use crate::cli::Environment; +use crate::output::Report; mod create; mod delete; @@ -52,15 +54,23 @@ pub enum ContextSubCommands { Watch(WatchCommand), } +impl Report for Context { + fn report(&self) { + println!("id: {}", self.id); + println!("application_id: {}", self.application_id); + println!("root_hash: {}", self.root_hash); + } +} + impl ContextCommand { - pub async fn run(self, context: CommandContext) -> EyreResult<()> { + pub async fn run(self, environment: &Environment) -> EyreResult<()> { match self.subcommand { - ContextSubCommands::Create(create) => create.run(context).await, - ContextSubCommands::Delete(delete) => delete.run(context).await, - ContextSubCommands::Get(get) => get.run(context).await, - ContextSubCommands::Join(join) => join.run(context).await, - ContextSubCommands::List(list) => list.run(context).await, - ContextSubCommands::Watch(watch) => watch.run(context).await, + ContextSubCommands::Create(create) => create.run(environment).await, + ContextSubCommands::Delete(delete) => delete.run(environment).await, + ContextSubCommands::Get(get) => get.run(environment).await, + ContextSubCommands::Join(join) => join.run(environment).await, + ContextSubCommands::List(list) => list.run(environment).await, + ContextSubCommands::Watch(watch) => watch.run(environment).await, } } } diff --git a/crates/meroctl/src/cli/context/create.rs b/crates/meroctl/src/cli/context/create.rs index 6c555231b..db369d162 100644 --- a/crates/meroctl/src/cli/context/create.rs +++ b/crates/meroctl/src/cli/context/create.rs @@ -10,6 +10,7 @@ use calimero_primitives::hash::Hash; use calimero_server_primitives::admin::{ CreateContextRequest, CreateContextResponse, GetApplicationResponse, InstallApplicationResponse, InstallDevApplicationRequest, UpdateContextApplicationRequest, + UpdateContextApplicationResponse, }; use camino::Utf8PathBuf; use clap::Parser; @@ -22,8 +23,9 @@ use reqwest::Client; use tokio::runtime::Handle; use tokio::sync::mpsc; -use crate::cli::CommandContext; -use crate::common::{fetch_multiaddr, get_response, load_config, multiaddr_to_url, RequestType}; +use crate::cli::Environment; +use crate::common::{do_request, fetch_multiaddr, load_config, multiaddr_to_url, RequestType}; +use crate::output::Report; #[derive(Debug, Parser)] #[command(about = "Create a new context")] @@ -64,9 +66,22 @@ pub struct CreateCommand { context_seed: Option, } +impl Report for CreateContextResponse { + fn report(&self) { + println!("id: {}", self.data.context_id); + println!("member_public_key: {}", self.data.member_public_key); + } +} + +impl Report for UpdateContextApplicationResponse { + fn report(&self) { + println!("Context application updated"); + } +} + impl CreateCommand { - pub async fn run(self, context: CommandContext) -> EyreResult<()> { - let config = load_config(&context.args.home, &context.args.node_name)?; + pub async fn run(self, environment: &Environment) -> EyreResult<()> { + let config = load_config(&environment.args.home, &environment.args.node_name)?; let multiaddr = fetch_multiaddr(&config)?; let client = Client::new(); @@ -79,6 +94,7 @@ impl CreateCommand { params, } => { let _ = create_context( + &environment, &client, &multiaddr, context_seed, @@ -99,6 +115,7 @@ impl CreateCommand { let metadata = metadata.map(String::into_bytes); let application_id = install_app( + &environment, &client, &multiaddr, path.clone(), @@ -108,6 +125,7 @@ impl CreateCommand { .await?; let context_id = create_context( + &environment, &client, &multiaddr, context_seed, @@ -118,6 +136,7 @@ impl CreateCommand { .await?; watch_app_and_update_context( + &environment, &client, &multiaddr, context_id, @@ -135,6 +154,7 @@ impl CreateCommand { } async fn create_context( + environment: &Environment, client: &Client, base_multiaddr: &Multiaddr, context_seed: Option, @@ -153,33 +173,16 @@ async fn create_context( params.map(String::into_bytes).unwrap_or_default(), ); - let response = get_response(client, url, Some(request), keypair, RequestType::Post).await?; - - if response.status().is_success() { - let context_response: CreateContextResponse = response.json().await?; - - let context_id = context_response.data.context_id; + let response: CreateContextResponse = + do_request(client, url, Some(request), keypair, RequestType::Post).await?; - println!("Context `\x1b[36m{context_id}\x1b[0m` created!"); + environment.output.write(&response); - println!( - "Context{{\x1b[36m{context_id}\x1b[0m}} -> Application{{\x1b[36m{application_id}\x1b[0m}}", - ); - - return Ok(context_id); - } - - let status = response.status(); - let error_text = response.text().await?; - - bail!( - "Request failed with status: {}. Error: {}", - status, - error_text - ); + Ok(response.data.context_id) } async fn watch_app_and_update_context( + environment: &Environment, client: &Client, base_multiaddr: &Multiaddr, context_id: ContextId, @@ -223,6 +226,7 @@ async fn watch_app_and_update_context( } let application_id = install_app( + environment, client, base_multiaddr, path.clone(), @@ -231,14 +235,22 @@ async fn watch_app_and_update_context( ) .await?; - update_context_application(client, base_multiaddr, context_id, application_id, keypair) - .await?; + update_context_application( + environment, + client, + base_multiaddr, + context_id, + application_id, + keypair, + ) + .await?; } Ok(()) } async fn update_context_application( + environment: &Environment, client: &Client, base_multiaddr: &Multiaddr, context_id: ContextId, @@ -252,24 +264,12 @@ async fn update_context_application( let request = UpdateContextApplicationRequest::new(application_id); - let response = get_response(client, url, Some(request), keypair, RequestType::Post).await?; - - if response.status().is_success() { - println!( - "Context{{\x1b[36m{context_id}\x1b[0m}} -> Application{{\x1b[36m{application_id}\x1b[0m}}" - ); - - return Ok(()); - } + let response: UpdateContextApplicationResponse = + do_request(client, url, Some(request), keypair, RequestType::Post).await?; - let status = response.status(); - let error_text = response.text().await?; + environment.output.write(&response); - bail!( - "Request failed with status: {}. Error: {}", - status, - error_text - ); + Ok(()) } async fn app_installed( @@ -283,18 +283,14 @@ async fn app_installed( &format!("admin-api/dev/application/{application_id}"), )?; - let response = get_response(client, url, None::<()>, keypair, RequestType::Get).await?; - - if !response.status().is_success() { - bail!("Request failed with status: {}", response.status()) - } - - let api_response: GetApplicationResponse = response.json().await?; + let response: GetApplicationResponse = + do_request(client, url, None::<()>, keypair, RequestType::Get).await?; - Ok(api_response.data.application.is_some()) + Ok(response.data.application.is_some()) } async fn install_app( + environment: &Environment, client: &Client, base_multiaddr: &Multiaddr, path: Utf8PathBuf, @@ -305,7 +301,7 @@ async fn install_app( let install_request = InstallDevApplicationRequest::new(path, metadata.unwrap_or_default()); - let install_response = get_response( + let response: InstallApplicationResponse = do_request( client, install_url, Some(install_request), @@ -314,24 +310,7 @@ async fn install_app( ) .await?; - if !install_response.status().is_success() { - let status = install_response.status(); - let error_text = install_response.text().await?; - bail!( - "Application installation failed with status: {}. Error: {}", - status, - error_text - ) - } - - let response = install_response - .json::() - .await?; - - println!( - "Application `\x1b[36m{}\x1b[0m` installed!", - response.data.application_id - ); + environment.output.write(&response); Ok(response.data.application_id) } diff --git a/crates/meroctl/src/cli/context/delete.rs b/crates/meroctl/src/cli/context/delete.rs index 06cb5fc7f..ebfa27b94 100644 --- a/crates/meroctl/src/cli/context/delete.rs +++ b/crates/meroctl/src/cli/context/delete.rs @@ -1,11 +1,11 @@ +use calimero_server_primitives::admin::DeleteContextResponse; use clap::Parser; -use eyre::{bail, Result as EyreResult}; -use libp2p::identity::Keypair; -use libp2p::Multiaddr; +use eyre::Result as EyreResult; use reqwest::Client; -use crate::cli::CommandContext; -use crate::common::{fetch_multiaddr, get_response, load_config, multiaddr_to_url, RequestType}; +use crate::cli::Environment; +use crate::common::{do_request, fetch_multiaddr, load_config, multiaddr_to_url, RequestType}; +use crate::output::Report; #[derive(Debug, Parser)] #[command(about = "Delete an context")] @@ -14,33 +14,32 @@ pub struct DeleteCommand { pub context_id: String, } -impl DeleteCommand { - pub async fn run(self, context: CommandContext) -> EyreResult<()> { - let config = load_config(&context.args.home, &context.args.node_name)?; - - self.delete_context(fetch_multiaddr(&config)?, &Client::new(), &config.identity) - .await +impl Report for DeleteContextResponse { + fn report(&self) { + println!("is_deleted: {}", self.data.is_deleted); } +} + +impl DeleteCommand { + pub async fn run(self, environment: &Environment) -> EyreResult<()> { + let config = load_config(&environment.args.home, &environment.args.node_name)?; - #[expect(clippy::print_stdout, reason = "Acceptable for CLI")] - async fn delete_context( - &self, - multiaddr: &Multiaddr, - client: &Client, - keypair: &Keypair, - ) -> EyreResult<()> { let url = multiaddr_to_url( - multiaddr, + fetch_multiaddr(&config)?, &format!("admin-api/dev/contexts/{}", self.context_id), )?; - let response = get_response(client, url, None::<()>, keypair, RequestType::Delete).await?; - if !response.status().is_success() { - bail!("Request failed with status: {}", response.status()) - } + let response: DeleteContextResponse = do_request( + &Client::new(), + url, + None::<()>, + &config.identity, + RequestType::Delete, + ) + .await?; + + environment.output.write(&response); - let text = response.text().await?; - println!("Context deleted successfully: {text}"); Ok(()) } } diff --git a/crates/meroctl/src/cli/context/get.rs b/crates/meroctl/src/cli/context/get.rs index 72997e3a8..7930e153d 100644 --- a/crates/meroctl/src/cli/context/get.rs +++ b/crates/meroctl/src/cli/context/get.rs @@ -1,11 +1,18 @@ +use calimero_server_primitives::admin::{ + GetContextClientKeysResponse, GetContextIdentitiesResponse, GetContextResponse, + GetContextStorageResponse, GetContextUsersResponse, +}; use clap::{Parser, ValueEnum}; -use eyre::{bail, Result as EyreResult}; +use eyre::Result as EyreResult; use libp2p::identity::Keypair; use libp2p::Multiaddr; use reqwest::Client; +use serde::de::DeserializeOwned; +use serde::Serialize; -use crate::cli::CommandContext; -use crate::common::{fetch_multiaddr, get_response, load_config, multiaddr_to_url, RequestType}; +use crate::cli::Environment; +use crate::common::{do_request, fetch_multiaddr, load_config, multiaddr_to_url, RequestType}; +use crate::output::Report; #[derive(Parser, Debug)] #[command(about = "Fetch details about the context")] @@ -16,6 +23,7 @@ pub struct GetCommand { #[arg(value_name = "CONTEXT_ID", help = "context_id of the context")] pub context_id: String, } + #[derive(Clone, Debug, ValueEnum)] pub enum GetRequest { Context, @@ -25,40 +33,72 @@ pub enum GetRequest { Identities, } +impl Report for GetContextResponse { + fn report(&self) { + self.data.report() + } +} + +impl Report for GetContextUsersResponse { + fn report(&self) { + for user in &self.data.context_users { + println!("user_id: {}", user.user_id); + println!("joined_at: {}", user.joined_at); + } + } +} + +impl Report for GetContextClientKeysResponse { + fn report(&self) { + println!("Client Keys: {:?}", self) + } +} + +impl Report for GetContextStorageResponse { + fn report(&self) { + println!("Storage: {:?}", self) + } +} + +impl Report for GetContextIdentitiesResponse { + fn report(&self) { + println!("Identities: {:?}", self) + } +} + impl GetCommand { - pub async fn run(self, context: CommandContext) -> EyreResult<()> { - let config = load_config(&context.args.home, &context.args.node_name)?; + pub async fn run(self, environment: &Environment) -> EyreResult<()> { + let config = load_config(&environment.args.home, &environment.args.node_name)?; let multiaddr = fetch_multiaddr(&config)?; let client = Client::new(); match self.method { GetRequest::Context => { - self.get_context(&multiaddr, &client, &config.identity) - .await?; + self.get_context(&environment, &multiaddr, &client, &config.identity) + .await } GetRequest::Users => { - self.get_users(&multiaddr, &client, &config.identity) - .await? + self.get_users(&environment, &multiaddr, &client, &config.identity) + .await } GetRequest::ClientKeys => { - self.get_client_keys(&multiaddr, &client, &config.identity) - .await?; + self.get_client_keys(&environment, &multiaddr, &client, &config.identity) + .await } GetRequest::Storage => { - self.get_storage(&multiaddr, &client, &config.identity) - .await?; + self.get_storage(&environment, &multiaddr, &client, &config.identity) + .await } GetRequest::Identities => { - self.get_identities(&multiaddr, &client, &config.identity) - .await?; + self.get_identities(&environment, &multiaddr, &client, &config.identity) + .await } } - - Ok(()) } async fn get_context( &self, + environment: &Environment, multiaddr: &Multiaddr, client: &Client, keypair: &Keypair, @@ -67,11 +107,13 @@ impl GetCommand { multiaddr, &format!("admin-api/dev/contexts/{}", self.context_id), )?; - self.make_request(client, url, keypair).await + self.make_request::(environment, client, url, keypair) + .await } async fn get_users( &self, + environment: &Environment, multiaddr: &Multiaddr, client: &Client, keypair: &Keypair, @@ -80,11 +122,13 @@ impl GetCommand { multiaddr, &format!("admin-api/dev/contexts/{}/users", self.context_id), )?; - self.make_request(client, url, keypair).await + self.make_request::(environment, client, url, keypair) + .await } async fn get_client_keys( &self, + environment: &Environment, multiaddr: &Multiaddr, client: &Client, keypair: &Keypair, @@ -93,11 +137,13 @@ impl GetCommand { multiaddr, &format!("admin-api/dev/contexts/{}/client-keys", self.context_id), )?; - self.make_request(client, url, keypair).await + self.make_request::(environment, client, url, keypair) + .await } async fn get_storage( &self, + environment: &Environment, multiaddr: &Multiaddr, client: &Client, keypair: &Keypair, @@ -106,11 +152,13 @@ impl GetCommand { multiaddr, &format!("admin-api/dev/contexts/{}/storage", self.context_id), )?; - self.make_request(client, url, keypair).await + self.make_request::(environment, client, url, keypair) + .await } async fn get_identities( &self, + environment: &Environment, multiaddr: &Multiaddr, client: &Client, keypair: &Keypair, @@ -119,24 +167,26 @@ impl GetCommand { multiaddr, &format!("admin-api/dev/contexts/{}/identities", self.context_id), )?; - self.make_request(client, url, keypair).await + self.make_request::(environment, client, url, keypair) + .await } #[expect(clippy::print_stdout, reason = "Acceptable for CLI")] - async fn make_request( + async fn make_request( &self, + environment: &Environment, client: &Client, url: reqwest::Url, keypair: &Keypair, - ) -> EyreResult<()> { - let response = get_response(client, url, None::<()>, keypair, RequestType::Get).await?; + ) -> EyreResult<()> + where + O: DeserializeOwned + Report + Serialize, + { + let response = + do_request::<(), O>(client, url, None::<()>, keypair, RequestType::Get).await?; - if !response.status().is_success() { - bail!("Request failed with status: {}", response.status()) - } + environment.output.write(&response); - let text = response.text().await?; - println!("{text}"); Ok(()) } } diff --git a/crates/meroctl/src/cli/context/join.rs b/crates/meroctl/src/cli/context/join.rs index c946b9034..d25699072 100644 --- a/crates/meroctl/src/cli/context/join.rs +++ b/crates/meroctl/src/cli/context/join.rs @@ -2,12 +2,12 @@ use calimero_primitives::context::ContextInvitationPayload; use calimero_primitives::identity::PrivateKey; use calimero_server_primitives::admin::{JoinContextRequest, JoinContextResponse}; use clap::Parser; -use eyre::{bail, Result as EyreResult}; +use eyre::Result as EyreResult; use reqwest::Client; -use tracing::info; -use crate::cli::CommandContext; -use crate::common::{fetch_multiaddr, get_response, load_config, multiaddr_to_url, RequestType}; +use crate::cli::Environment; +use crate::common::{do_request, fetch_multiaddr, load_config, multiaddr_to_url, RequestType}; +use crate::output::Report; #[derive(Debug, Parser)] #[command(about = "Join an application context")] @@ -24,11 +24,23 @@ pub struct JoinCommand { invitation_payload: ContextInvitationPayload, } +impl Report for JoinContextResponse { + fn report(&self) { + match self.data { + Some(ref payload) => { + print!("context_id {}", payload.context_id); + print!("member_public_key: {}", payload.member_public_key); + } + None => todo!(), + } + } +} + impl JoinCommand { - pub async fn run(self, context: CommandContext) -> EyreResult<()> { - let config = load_config(&context.args.home, &context.args.node_name)?; + pub async fn run(self, environment: &Environment) -> EyreResult<()> { + let config = load_config(&environment.args.home, &environment.args.node_name)?; - let response = get_response( + let response: JoinContextResponse = do_request( &Client::new(), multiaddr_to_url(fetch_multiaddr(&config)?, "admin-api/dev/contexts/join")?, Some(JoinContextRequest::new( @@ -40,18 +52,7 @@ impl JoinCommand { ) .await?; - if !response.status().is_success() { - bail!("Request failed with status: {}", response.status()) - } - - let Some(body) = response.json::().await?.data else { - bail!("Unable to join context"); - }; - - info!( - "Context {} sucesfully joined as {}", - body.context_id, body.member_public_key - ); + environment.output.write(&response); Ok(()) } diff --git a/crates/meroctl/src/cli/context/list.rs b/crates/meroctl/src/cli/context/list.rs index e17292fea..6ec7d9f2c 100644 --- a/crates/meroctl/src/cli/context/list.rs +++ b/crates/meroctl/src/cli/context/list.rs @@ -1,20 +1,29 @@ use calimero_server_primitives::admin::GetContextsResponse; use clap::Parser; -use eyre::{bail, Result as EyreResult}; +use eyre::Result as EyreResult; use reqwest::Client; -use crate::cli::CommandContext; -use crate::common::{fetch_multiaddr, get_response, load_config, multiaddr_to_url, RequestType}; +use crate::cli::Environment; +use crate::common::{do_request, fetch_multiaddr, load_config, multiaddr_to_url, RequestType}; +use crate::output::Report; #[derive(Debug, Parser)] #[command(about = "List all contexts")] pub struct ListCommand; +impl Report for GetContextsResponse { + fn report(&self) { + for context in self.data.contexts.iter() { + context.report(); + } + } +} + impl ListCommand { - pub async fn run(self, context: CommandContext) -> EyreResult<()> { - let config = load_config(&context.args.home, &context.args.node_name)?; + pub async fn run(self, environment: &Environment) -> EyreResult<()> { + let config = load_config(&environment.args.home, &environment.args.node_name)?; - let response = get_response( + let response: GetContextsResponse = do_request( &Client::new(), multiaddr_to_url(fetch_multiaddr(&config)?, "admin-api/dev/contexts")?, None::<()>, @@ -23,17 +32,7 @@ impl ListCommand { ) .await?; - if !response.status().is_success() { - bail!("Request failed with status: {}", response.status()) - } - - let api_response: GetContextsResponse = response.json().await?; - let contexts = api_response.data.contexts; - - #[expect(clippy::print_stdout, reason = "Acceptable for CLI")] - for context in contexts { - println!("{}", context.id); - } + environment.output.write(&response); Ok(()) } diff --git a/crates/meroctl/src/cli/context/watch.rs b/crates/meroctl/src/cli/context/watch.rs index 50b71eb46..67e9c023a 100644 --- a/crates/meroctl/src/cli/context/watch.rs +++ b/crates/meroctl/src/cli/context/watch.rs @@ -5,7 +5,7 @@ use eyre::Result as EyreResult; use futures_util::{SinkExt, StreamExt}; use tokio_tungstenite::connect_async; -use crate::cli::CommandContext; +use crate::cli::Environment; use crate::common::{fetch_multiaddr, load_config, multiaddr_to_url}; #[derive(Debug, Parser)] @@ -17,8 +17,8 @@ pub struct WatchCommand { } impl WatchCommand { - pub async fn run(self, context: CommandContext) -> EyreResult<()> { - let config = load_config(&context.args.home, &context.args.node_name)?; + pub async fn run(self, environment: &Environment) -> EyreResult<()> { + let config = load_config(&environment.args.home, &environment.args.node_name)?; let mut url = multiaddr_to_url(fetch_multiaddr(&config)?, "ws")?; url.set_scheme("ws") diff --git a/crates/meroctl/src/cli/jsonrpc.rs b/crates/meroctl/src/cli/jsonrpc.rs index be9ace557..7ffec3c27 100644 --- a/crates/meroctl/src/cli/jsonrpc.rs +++ b/crates/meroctl/src/cli/jsonrpc.rs @@ -1,14 +1,15 @@ use calimero_primitives::context::ContextId; use calimero_server_primitives::jsonrpc::{ - ExecuteRequest, Request, RequestId, RequestPayload, Version, + ExecuteRequest, Request, RequestId, RequestPayload, Response, Version, }; use clap::{Parser, ValueEnum}; use const_format::concatcp; use eyre::{bail, Result as EyreResult}; use serde_json::Value; -use crate::cli::CommandContext; -use crate::common::{get_response, load_config, multiaddr_to_url, RequestType}; +use crate::cli::Environment; +use crate::common::{do_request, load_config, multiaddr_to_url, RequestType}; +use crate::output::Report; pub const EXAMPLES: &str = r" # Execute a RPC method call @@ -48,10 +49,18 @@ pub enum CallType { Execute, } +impl Report for Response { + fn report(&self) { + println!("jsonrpc: {:#?}", self.jsonrpc); + println!("id: {:?}", self.id); + println!("result: {:#?}", self.body); + } +} + #[expect(clippy::print_stdout, reason = "Acceptable for CLI")] impl CallCommand { - pub async fn run(self, context: CommandContext) -> EyreResult<()> { - let config = load_config(&context.args.home, &context.args.node_name)?; + pub async fn run(self, environment: &Environment) -> EyreResult<()> { + let config = load_config(&environment.args.home, &environment.args.node_name)?; let Some(multiaddr) = config.network.server.listen.first() else { bail!("No address.") @@ -85,7 +94,7 @@ impl CallCommand { } let client = reqwest::Client::new(); - let response = get_response( + let response: Response = do_request( &client, url, Some(request), @@ -94,7 +103,7 @@ impl CallCommand { ) .await?; - println!("Response: {}", response.text().await?); + environment.output.write(&response); Ok(()) } diff --git a/crates/meroctl/src/common.rs b/crates/meroctl/src/common.rs index b5c7babd9..cf16adc71 100644 --- a/crates/meroctl/src/common.rs +++ b/crates/meroctl/src/common.rs @@ -5,8 +5,10 @@ use eyre::{bail, eyre, Result as EyreResult}; use libp2p::identity::Keypair; use libp2p::multiaddr::Protocol; use libp2p::Multiaddr; -use reqwest::{Client, Response, Url}; -use serde::Serialize; +use reqwest::{Client, Url}; +use serde::{de::DeserializeOwned, Serialize}; + +use crate::cli::ApiError; pub fn multiaddr_to_url(multiaddr: &Multiaddr, api_path: &str) -> EyreResult { #[expect(clippy::wildcard_enum_match_arm, reason = "Acceptable here")] @@ -32,15 +34,16 @@ pub fn multiaddr_to_url(multiaddr: &Multiaddr, api_path: &str) -> EyreResult( +pub async fn do_request( client: &Client, url: Url, - body: Option, + body: Option, keypair: &Keypair, req_type: RequestType, -) -> EyreResult +) -> EyreResult where - S: Serialize, + I: Serialize, + O: DeserializeOwned, { let timestamp = Utc::now().timestamp().to_string(); let signature = keypair.sign(timestamp.as_bytes())?; @@ -55,11 +58,67 @@ where .header("X-Signature", bs58::encode(signature).into_string()) .header("X-Timestamp", timestamp); - builder - .send() - .await - .map_err(|_| eyre!("Error with client request")) + let response = builder.send().await?; + + if !response.status().is_success() { + bail!(ApiError { + status_code: response.status().as_u16(), + message: response.text().await?, + }); + } + + let result = response.json::().await?; + + return Ok(result); } +// pub async fn do_request( +// client: &Client, +// url: Url, +// body: Option, +// keypair: &Keypair, +// req_type: RequestType, +// ) -> Result +// where +// I: Serialize, +// O: DeserializeOwned, +// { +// let timestamp = Utc::now().timestamp().to_string(); +// let signature = keypair +// .sign(timestamp.as_bytes()) +// .map_err(|err| ServerRequestError::SigningError(err.to_string()))?; + +// let mut builder = match req_type { +// RequestType::Get => client.get(url), +// RequestType::Post => client.post(url).json(&body), +// RequestType::Delete => client.delete(url), +// }; + +// builder = builder +// .header("X-Signature", bs58::encode(signature).into_string()) +// .header("X-Timestamp", timestamp); + +// let response = builder +// .send() +// .await +// .map_err(|err| ServerRequestError::ExecutionError(err.to_string()))?; + +// if !response.status().is_success() { +// return Err(ServerRequestError::ApiError(ApiError { +// status_code: response.status().as_u16(), +// message: response +// .text() +// .await +// .map_err(|err| ServerRequestError::DeserializeError(err.to_string()))?, +// })); +// } + +// let result = response +// .json::() +// .await +// .map_err(|err| ServerRequestError::DeserializeError(err.to_string()))?; + +// return Ok(result); +// } pub fn load_config(home: &Utf8Path, node_name: &str) -> EyreResult { let path = home.join(node_name); @@ -88,11 +147,3 @@ pub enum RequestType { Post, Delete, } -pub async fn craft_failed_request_message(response: Response, message: &str) -> EyreResult { - let status = response.status(); - let error_text = response.text().await?; - Ok(format!( - "{} - Status: {}, Error: {}", - message, status, error_text - )) -} diff --git a/crates/meroctl/src/main.rs b/crates/meroctl/src/main.rs index 5d9021b00..1021a77e0 100644 --- a/crates/meroctl/src/main.rs +++ b/crates/meroctl/src/main.rs @@ -1,11 +1,7 @@ -use std::env::var; +use std::process::ExitCode; use calimero_server as _; use clap::Parser; -use eyre::Result as EyreResult; -use tracing_subscriber::fmt::layer; -use tracing_subscriber::prelude::*; -use tracing_subscriber::{registry, EnvFilter}; use crate::cli::RootCommand; @@ -15,19 +11,16 @@ mod defaults; mod output; #[tokio::main] -async fn main() -> EyreResult<()> { - setup()?; +async fn main() -> ExitCode { + if let Err(err) = color_eyre::install() { + eprintln!("Failed to install color_eyre: {}", err); + return ExitCode::FAILURE; + } let command = RootCommand::parse(); - command.run().await -} - -fn setup() -> EyreResult<()> { - registry() - .with(EnvFilter::builder().parse(format!("info,{}", var("RUST_LOG").unwrap_or_default()))?) - .with(layer()) - .init(); - - color_eyre::install() + match command.run().await { + Ok(_) => ExitCode::SUCCESS, + Err(err) => err.into(), + } } diff --git a/crates/meroctl/src/output.rs b/crates/meroctl/src/output.rs index d18232e2c..c68066a61 100644 --- a/crates/meroctl/src/output.rs +++ b/crates/meroctl/src/output.rs @@ -13,6 +13,10 @@ pub struct Output { format: Format, } +pub trait Report { + fn report(&self); +} + impl Output { pub fn new(output_type: Format) -> Self { Output { @@ -20,13 +24,13 @@ impl Output { } } - pub fn write_output(&self, value: T) { + pub fn write(&self, value: &T) { match self.format { Format::Json => match serde_json::to_string(&value) { Ok(json) => println!("{}", json), Err(e) => eprintln!("Failed to serialize to JSON: {}", e), }, - Format::PlainText => println!("{}", value), + Format::PlainText => value.report(), } } } diff --git a/crates/server-primitives/src/admin.rs b/crates/server-primitives/src/admin.rs index 68122c932..5857f7096 100644 --- a/crates/server-primitives/src/admin.rs +++ b/crates/server-primitives/src/admin.rs @@ -1,12 +1,17 @@ use calimero_primitives::application::{Application, ApplicationId}; use calimero_primitives::context::{Context, ContextId, ContextInvitationPayload}; use calimero_primitives::hash::Hash; -use calimero_primitives::identity::{PrivateKey, PublicKey, WalletType}; +use calimero_primitives::identity::{ClientKey, ContextUser, PrivateKey, PublicKey, WalletType}; use camino::Utf8PathBuf; use serde::{Deserialize, Serialize}; use serde_json::Value; use url::Url; +#[derive(Clone, Copy, Debug, Deserialize, Serialize)] +#[expect(clippy::exhaustive_structs, reason = "Exhaustive")] +pub struct Empty; + +// -------------------------------------------- Application API -------------------------------------------- #[derive(Clone, Debug, Deserialize, Serialize)] #[non_exhaustive] pub struct InstallApplicationRequest { @@ -15,13 +20,6 @@ pub struct InstallApplicationRequest { pub metadata: Vec, } -#[derive(Clone, Copy, Debug, Deserialize, Serialize)] -#[serde(rename_all = "camelCase")] -#[non_exhaustive] -pub struct UninstallApplicationRequest { - pub application_id: ApplicationId, -} - impl InstallApplicationRequest { #[must_use] pub const fn new(url: Url, hash: Option, metadata: Vec) -> Self { @@ -33,6 +31,27 @@ impl InstallApplicationRequest { } } +#[derive(Clone, Copy, Debug, Deserialize, Serialize)] +#[non_exhaustive] +pub struct ApplicationInstallResponseData { + pub application_id: ApplicationId, +} + +#[derive(Clone, Copy, Debug, Deserialize, Serialize)] +#[non_exhaustive] +pub struct InstallApplicationResponse { + pub data: ApplicationInstallResponseData, +} + +impl InstallApplicationResponse { + #[must_use] + pub const fn new(application_id: ApplicationId) -> Self { + Self { + data: ApplicationInstallResponseData { application_id }, + } + } +} + #[derive(Clone, Debug, Deserialize, Serialize)] #[non_exhaustive] pub struct InstallDevApplicationRequest { @@ -47,28 +66,57 @@ impl InstallDevApplicationRequest { } } +#[derive(Clone, Copy, Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +#[non_exhaustive] +pub struct UninstallApplicationRequest { + pub application_id: ApplicationId, +} + +#[derive(Clone, Copy, Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +#[non_exhaustive] +pub struct UninstallApplicationResponseData { + pub application_id: ApplicationId, +} + +#[derive(Clone, Copy, Debug, Deserialize, Serialize)] +#[non_exhaustive] +pub struct UninstallApplicationResponse { + pub data: UninstallApplicationResponseData, +} + +impl UninstallApplicationResponse { + #[must_use] + pub const fn new(application_id: ApplicationId) -> Self { + Self { + data: UninstallApplicationResponseData { application_id }, + } + } +} + #[derive(Clone, Debug, Deserialize, Serialize)] #[non_exhaustive] -pub struct ApplicationListResult { +pub struct ListApplicationResponseData { pub apps: Vec, } #[derive(Debug, Deserialize, Serialize)] #[non_exhaustive] pub struct ListApplicationsResponse { - pub data: ApplicationListResult, + pub data: ListApplicationResponseData, } impl ListApplicationsResponse { #[must_use] pub const fn new(apps: Vec) -> Self { Self { - data: ApplicationListResult { apps }, + data: ListApplicationResponseData { apps }, } } } -#[derive(Clone, Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, Serialize)] #[non_exhaustive] pub struct GetApplicationDetailsResponse { pub data: Application, @@ -81,70 +129,273 @@ impl GetApplicationDetailsResponse { } } -#[derive(Clone, Copy, Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, Serialize)] #[non_exhaustive] -pub struct InstallApplicationResponse { - pub data: ApplicationInstallResult, +pub struct GetApplicationResponse { + pub data: GetApplicationResult, } -impl InstallApplicationResponse { +impl GetApplicationResponse { #[must_use] - pub const fn new(application_id: ApplicationId) -> Self { + pub const fn new(application: Option) -> Self { Self { - data: ApplicationInstallResult { application_id }, + data: GetApplicationResult { application }, } } } -#[derive(Clone, Copy, Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, Serialize)] #[non_exhaustive] -pub struct UninstallApplicationResponse { - pub data: ApplicationUninstallResult, +pub struct GetApplicationResult { + pub application: Option, } -impl UninstallApplicationResponse { +// -------------------------------------------- Context API -------------------------------------------- +#[derive(Debug, Deserialize, Serialize)] +#[non_exhaustive] +pub struct GetContextsResponse { + pub data: ContextList, +} + +impl GetContextsResponse { #[must_use] - pub const fn new(application_id: ApplicationId) -> Self { + pub const fn new(contexts: Vec) -> Self { Self { - data: ApplicationUninstallResult { application_id }, + data: ContextList { contexts }, } } } -#[derive(Clone, Copy, Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, Serialize)] #[non_exhaustive] -pub struct ApplicationInstallResult { - pub application_id: ApplicationId, +pub struct ContextList { + pub contexts: Vec, } -#[derive(Clone, Copy, Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] #[non_exhaustive] -pub struct ApplicationUninstallResult { +pub struct CreateContextRequest { pub application_id: ApplicationId, + pub context_seed: Option, + pub initialization_params: Vec, +} + +impl CreateContextRequest { + #[must_use] + pub const fn new( + application_id: ApplicationId, + context_seed: Option, + initialization_params: Vec, + ) -> Self { + Self { + application_id, + context_seed, + initialization_params, + } + } } #[derive(Debug, Deserialize, Serialize)] #[non_exhaustive] -pub struct GetApplicationResponse { - pub data: GetApplicationResult, +pub struct CreateContextResponseData { + pub context_id: ContextId, + pub member_public_key: PublicKey, } -impl GetApplicationResponse { +#[derive(Debug, Deserialize, Serialize)] +#[non_exhaustive] +pub struct CreateContextResponse { + pub data: CreateContextResponseData, +} + +impl CreateContextResponse { #[must_use] - pub const fn new(application: Option) -> Self { + pub const fn new(context_id: ContextId, member_public_key: PublicKey) -> Self { Self { - data: GetApplicationResult { application }, + data: CreateContextResponseData { + context_id, + member_public_key, + }, } } } +#[derive(Clone, Copy, Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct DeletedContextResponseData { + pub is_deleted: bool, +} + +#[derive(Clone, Copy, Debug, Deserialize, Serialize)] +pub struct DeleteContextResponse { + pub data: DeletedContextResponseData, +} + #[derive(Debug, Deserialize, Serialize)] #[non_exhaustive] -pub struct GetApplicationResult { - pub application: Option, +pub struct InviteToContextRequest { + pub context_id: ContextId, + pub inviter_id: PublicKey, + pub invitee_id: PublicKey, +} + +impl InviteToContextRequest { + #[must_use] + pub const fn new(context_id: ContextId, inviter_id: PublicKey, invitee_id: PublicKey) -> Self { + Self { + context_id, + inviter_id, + invitee_id, + } + } +} + +#[derive(Debug, Deserialize, Serialize)] +#[non_exhaustive] +pub struct InviteToContextResponse { + pub invitation_payload: Option, +} + +impl InviteToContextResponse { + #[must_use] + pub const fn new(invitation_payload: Option) -> Self { + Self { invitation_payload } + } +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[non_exhaustive] +pub struct JoinContextRequest { + pub private_key: PrivateKey, + pub invitation_payload: ContextInvitationPayload, +} + +impl JoinContextRequest { + #[must_use] + pub const fn new( + private_key: PrivateKey, + invitation_payload: ContextInvitationPayload, + ) -> Self { + Self { + private_key, + invitation_payload, + } + } +} + +#[derive(Copy, Clone, Debug, Deserialize, Serialize)] +#[non_exhaustive] +pub struct JoinContextResponseData { + pub context_id: ContextId, + pub member_public_key: PublicKey, +} + +impl JoinContextResponseData { + #[must_use] + pub const fn new(context_id: ContextId, member_public_key: PublicKey) -> Self { + Self { + context_id, + member_public_key, + } + } +} + +#[derive(Copy, Clone, Debug, Deserialize, Serialize)] +#[non_exhaustive] +pub struct JoinContextResponse { + pub data: Option, +} + +impl JoinContextResponse { + #[must_use] + pub const fn new(data: Option) -> Self { + Self { data } + } +} + +#[derive(Clone, Copy, Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +#[non_exhaustive] +pub struct UpdateContextApplicationRequest { + pub application_id: ApplicationId, +} + +impl UpdateContextApplicationRequest { + #[must_use] + pub const fn new(application_id: ApplicationId) -> Self { + Self { application_id } + } } +#[derive(Clone, Copy, Debug, Deserialize, Serialize)] +pub struct UpdateContextApplicationResponse { + pub data: Empty, +} + +#[derive(Clone, Copy, Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +#[non_exhaustive] +pub struct GetContextStorageResponseData { + pub size_in_bytes: u64, +} + +#[derive(Clone, Copy, Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct GetContextStorageResponse { + pub data: GetContextStorageResponseData, +} + +impl GetContextStorageResponse { + #[must_use] + pub const fn new(size_in_bytes: u64) -> Self { + Self { + data: GetContextStorageResponseData { size_in_bytes }, + } + } +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct GetContextResponse { + pub data: Context, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ContextIdentitiesResponseData { + pub identities: Vec, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct GetContextIdentitiesResponse { + pub data: ContextIdentitiesResponseData, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct GetContextClientKeysResponseData { + pub client_keys: Vec, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct GetContextClientKeysResponse { + pub data: GetContextClientKeysResponseData, +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct GetContextUsersResponseData { + pub context_users: Vec, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct GetContextUsersResponse { + pub data: GetContextUsersResponseData, +} + +// -------------------------------------------- Misc API -------------------------------------------- #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] #[non_exhaustive] @@ -362,189 +613,3 @@ impl NodeChallengeMessage { } } } - -#[derive(Clone, Copy, Debug, Deserialize, Serialize)] -#[serde(rename_all = "camelCase")] -#[non_exhaustive] -pub struct ContextStorage { - pub size_in_bytes: u64, -} - -impl ContextStorage { - #[must_use] - pub const fn new(size_in_bytes: u64) -> Self { - Self { size_in_bytes } - } -} - -#[derive(Debug, Deserialize, Serialize)] -#[non_exhaustive] -pub struct ContextList { - pub contexts: Vec, -} - -#[derive(Debug, Deserialize, Serialize)] -#[non_exhaustive] -pub struct GetContextsResponse { - pub data: ContextList, -} - -impl GetContextsResponse { - #[must_use] - pub const fn new(contexts: Vec) -> Self { - Self { - data: ContextList { contexts }, - } - } -} - -#[derive(Clone, Debug, Deserialize, Serialize)] -#[serde(rename_all = "camelCase")] -#[non_exhaustive] -pub struct CreateContextRequest { - pub application_id: ApplicationId, - pub context_seed: Option, - pub initialization_params: Vec, -} - -impl CreateContextRequest { - #[must_use] - pub const fn new( - application_id: ApplicationId, - context_seed: Option, - initialization_params: Vec, - ) -> Self { - Self { - application_id, - context_seed, - initialization_params, - } - } -} -#[derive(Debug, Deserialize, Serialize)] -#[non_exhaustive] -pub struct InviteToContextRequest { - pub context_id: ContextId, - pub inviter_id: PublicKey, - pub invitee_id: PublicKey, -} - -impl InviteToContextRequest { - #[must_use] - pub const fn new(context_id: ContextId, inviter_id: PublicKey, invitee_id: PublicKey) -> Self { - Self { - context_id, - inviter_id, - invitee_id, - } - } -} - -#[derive(Debug, Deserialize, Serialize)] -#[non_exhaustive] -pub struct InviteToContextResponse { - pub invitation_payload: Option, -} - -impl InviteToContextResponse { - #[must_use] - pub const fn new(invitation_payload: Option) -> Self { - Self { invitation_payload } - } -} - -#[derive(Debug, Deserialize, Serialize)] -#[non_exhaustive] -pub struct InviteToContextResponseData { - pub context_id: ContextId, - pub invitee_public_key: PublicKey, -} - -#[derive(Debug, Deserialize, Serialize)] -#[non_exhaustive] -pub struct ContextResponse { - pub context_id: ContextId, - pub member_public_key: PublicKey, -} - -#[derive(Debug, Deserialize, Serialize)] -#[non_exhaustive] -pub struct CreateContextResponse { - pub data: ContextResponse, -} - -impl CreateContextResponse { - #[must_use] - pub const fn new(context_id: ContextId, member_public_key: PublicKey) -> Self { - Self { - data: ContextResponse { - context_id, - member_public_key, - }, - } - } -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -#[non_exhaustive] -pub struct JoinContextRequest { - pub private_key: PrivateKey, - pub invitation_payload: ContextInvitationPayload, -} - -impl JoinContextRequest { - #[must_use] - pub const fn new( - private_key: PrivateKey, - invitation_payload: ContextInvitationPayload, - ) -> Self { - Self { - private_key, - invitation_payload, - } - } -} - -#[derive(Copy, Clone, Debug, Deserialize, Serialize)] -#[non_exhaustive] -pub struct JoinContextResponseData { - pub context_id: ContextId, - pub member_public_key: PublicKey, -} - -impl JoinContextResponseData { - #[must_use] - pub const fn new(context_id: ContextId, member_public_key: PublicKey) -> Self { - Self { - context_id, - member_public_key, - } - } -} - -#[derive(Copy, Clone, Debug, Serialize, Deserialize)] -#[non_exhaustive] -pub struct JoinContextResponse { - pub data: Option, -} - -impl JoinContextResponse { - #[must_use] - pub const fn new(data: Option) -> Self { - Self { data } - } -} - -#[derive(Clone, Copy, Debug, Deserialize, Serialize)] -#[serde(rename_all = "camelCase")] -#[non_exhaustive] -pub struct UpdateContextApplicationRequest { - pub application_id: ApplicationId, -} - -impl UpdateContextApplicationRequest { - #[must_use] - pub const fn new(application_id: ApplicationId) -> Self { - Self { application_id } - } -} diff --git a/crates/server/src/admin/handlers/context.rs b/crates/server/src/admin/handlers/context.rs index 0317bcba2..296804572 100644 --- a/crates/server/src/admin/handlers/context.rs +++ b/crates/server/src/admin/handlers/context.rs @@ -8,4 +8,4 @@ pub mod get_context_users; pub mod get_contexts; pub mod invite_to_context; pub mod join_context; -pub mod update_application_id; +pub mod update_context_application; diff --git a/crates/server/src/admin/handlers/context/delete_context.rs b/crates/server/src/admin/handlers/context/delete_context.rs index 0081dd13d..8bf6f320b 100644 --- a/crates/server/src/admin/handlers/context/delete_context.rs +++ b/crates/server/src/admin/handlers/context/delete_context.rs @@ -5,24 +5,13 @@ use axum::extract::Path; use axum::response::IntoResponse; use axum::Extension; use calimero_primitives::context::ContextId; +use calimero_server_primitives::admin::{DeleteContextResponse, DeletedContextResponseData}; use reqwest::StatusCode; -use serde::{Deserialize, Serialize}; use tower_sessions::Session; use crate::admin::service::{parse_api_error, ApiError, ApiResponse}; use crate::AdminState; -#[derive(Clone, Copy, Debug, Deserialize, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct DeletedContext { - is_deleted: bool, -} - -#[derive(Clone, Copy, Debug, Deserialize, Serialize)] -pub struct DeleteContextResponse { - data: DeletedContext, -} - pub async fn handler( Path(context_id): Path, _session: Session, @@ -46,7 +35,7 @@ pub async fn handler( match result { Ok(result) => ApiResponse { payload: DeleteContextResponse { - data: DeletedContext { is_deleted: result }, + data: DeletedContextResponseData { is_deleted: result }, }, } .into_response(), diff --git a/crates/server/src/admin/handlers/context/get_context.rs b/crates/server/src/admin/handlers/context/get_context.rs index 290bb3551..cc9730d5e 100644 --- a/crates/server/src/admin/handlers/context/get_context.rs +++ b/crates/server/src/admin/handlers/context/get_context.rs @@ -3,25 +3,13 @@ use std::sync::Arc; use axum::extract::Path; use axum::response::IntoResponse; use axum::Extension; -use calimero_primitives::context::{Context, ContextId}; -use calimero_primitives::identity::PublicKey; +use calimero_primitives::context::ContextId; +use calimero_server_primitives::admin::GetContextResponse; use reqwest::StatusCode; -use serde::{Deserialize, Serialize}; use crate::admin::service::{parse_api_error, ApiError, ApiResponse}; use crate::AdminState; -#[derive(Debug, Deserialize, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct ContextObject { - context: Context, -} - -#[derive(Debug, Deserialize, Serialize)] -pub struct GetContextResponse { - data: ContextObject, -} - pub async fn handler( Path(context_id): Path, Extension(state): Extension>, @@ -36,9 +24,7 @@ pub async fn handler( match context { Ok(ctx) => match ctx { Some(context) => ApiResponse { - payload: GetContextResponse { - data: ContextObject { context }, - }, + payload: GetContextResponse { data: context }, } .into_response(), None => ApiError { @@ -50,14 +36,3 @@ pub async fn handler( Err(err) => err.into_response(), } } - -#[derive(Debug, Deserialize, Serialize)] -pub struct GetContextIdentitiesResponse { - data: ContextIdentities, -} - -#[derive(Debug, Deserialize, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct ContextIdentities { - identities: Vec, -} diff --git a/crates/server/src/admin/handlers/context/get_context_client_keys.rs b/crates/server/src/admin/handlers/context/get_context_client_keys.rs index 461a11df4..4ff1b19be 100644 --- a/crates/server/src/admin/handlers/context/get_context_client_keys.rs +++ b/crates/server/src/admin/handlers/context/get_context_client_keys.rs @@ -4,24 +4,14 @@ use axum::extract::Path; use axum::response::IntoResponse; use axum::Extension; use calimero_primitives::context::ContextId; -use calimero_primitives::identity::ClientKey; -use serde::{Deserialize, Serialize}; +use calimero_server_primitives::admin::{ + GetContextClientKeysResponse, GetContextClientKeysResponseData, +}; use crate::admin::service::{parse_api_error, ApiResponse}; use crate::admin::storage::client_keys::get_context_client_key; use crate::AdminState; -#[derive(Debug, Deserialize, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct ClientKeys { - client_keys: Vec, -} - -#[derive(Debug, Deserialize, Serialize)] -pub struct GetContextClientKeysResponse { - data: ClientKeys, -} - pub async fn handler( Path(context_id): Path, Extension(state): Extension>, @@ -32,7 +22,7 @@ pub async fn handler( match client_keys_result { Ok(client_keys) => ApiResponse { payload: GetContextClientKeysResponse { - data: ClientKeys { client_keys }, + data: GetContextClientKeysResponseData { client_keys }, }, } .into_response(), diff --git a/crates/server/src/admin/handlers/context/get_context_identities.rs b/crates/server/src/admin/handlers/context/get_context_identities.rs index d4ff15e77..670c8972d 100644 --- a/crates/server/src/admin/handlers/context/get_context_identities.rs +++ b/crates/server/src/admin/handlers/context/get_context_identities.rs @@ -4,25 +4,15 @@ use axum::extract::Path; use axum::response::IntoResponse; use axum::Extension; use calimero_primitives::context::ContextId; -use calimero_primitives::identity::PublicKey; +use calimero_server_primitives::admin::{ + ContextIdentitiesResponseData, GetContextIdentitiesResponse, +}; use reqwest::StatusCode; -use serde::{Deserialize, Serialize}; use tracing::error; use crate::admin::service::{parse_api_error, ApiError, ApiResponse}; use crate::AdminState; -#[derive(Debug, Deserialize, Serialize)] -pub struct GetContextIdentitiesResponse { - data: ContextIdentities, -} - -#[derive(Debug, Deserialize, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct ContextIdentities { - identities: Vec, -} - pub async fn handler( Path(context_id): Path, Extension(state): Extension>, @@ -44,7 +34,7 @@ pub async fn handler( match context_identities { Ok(identities) => ApiResponse { payload: GetContextIdentitiesResponse { - data: ContextIdentities { identities }, + data: ContextIdentitiesResponseData { identities }, }, } .into_response(), diff --git a/crates/server/src/admin/handlers/context/get_context_storage.rs b/crates/server/src/admin/handlers/context/get_context_storage.rs index 2574dd993..db7d00e86 100644 --- a/crates/server/src/admin/handlers/context/get_context_storage.rs +++ b/crates/server/src/admin/handlers/context/get_context_storage.rs @@ -3,26 +3,11 @@ use std::sync::Arc; use axum::extract::Path; use axum::response::IntoResponse; use axum::Extension; -use calimero_server_primitives::admin::ContextStorage; -use serde::Serialize; +use calimero_server_primitives::admin::GetContextStorageResponse; use crate::admin::service::ApiResponse; use crate::AdminState; -#[derive(Debug, Serialize)] -struct GetContextStorageResponse { - data: ContextStorage, -} - -impl GetContextStorageResponse { - #[must_use] - pub const fn new(size_in_bytes: u64) -> Self { - Self { - data: ContextStorage::new(size_in_bytes), - } - } -} - pub async fn handler( Path(_context_id): Path, Extension(_state): Extension>, diff --git a/crates/server/src/admin/handlers/context/get_context_users.rs b/crates/server/src/admin/handlers/context/get_context_users.rs index 4ed0dd994..2351c85b7 100644 --- a/crates/server/src/admin/handlers/context/get_context_users.rs +++ b/crates/server/src/admin/handlers/context/get_context_users.rs @@ -3,30 +3,18 @@ use std::sync::Arc; use axum::extract::Path; use axum::response::IntoResponse; use axum::Extension; -use calimero_primitives::identity::ContextUser; -use serde::{Deserialize, Serialize}; +use calimero_server_primitives::admin::{GetContextUsersResponse, GetContextUsersResponseData}; use crate::admin::service::ApiResponse; use crate::AdminState; -#[derive(Debug, Deserialize, Serialize)] -#[serde(rename_all = "camelCase")] -struct ContextUsers { - context_users: Vec, -} - -#[derive(Debug, Deserialize, Serialize)] -pub struct GetContextUsersResponse { - data: ContextUsers, -} - pub async fn handler( Path(_context_id): Path, Extension(_state): Extension>, ) -> impl IntoResponse { ApiResponse { payload: GetContextUsersResponse { - data: ContextUsers { + data: GetContextUsersResponseData { context_users: vec![], }, }, diff --git a/crates/server/src/admin/handlers/context/update_application_id.rs b/crates/server/src/admin/handlers/context/update_context_application.rs similarity index 79% rename from crates/server/src/admin/handlers/context/update_application_id.rs rename to crates/server/src/admin/handlers/context/update_context_application.rs index 729c9fd44..523cda1f3 100644 --- a/crates/server/src/admin/handlers/context/update_application_id.rs +++ b/crates/server/src/admin/handlers/context/update_context_application.rs @@ -5,18 +5,14 @@ use axum::extract::Path; use axum::response::IntoResponse; use axum::{Extension, Json}; use calimero_primitives::context::ContextId; -use calimero_server_primitives::admin::UpdateContextApplicationRequest; +use calimero_server_primitives::admin::{ + Empty, UpdateContextApplicationRequest, UpdateContextApplicationResponse, +}; use reqwest::StatusCode; -use serde::Serialize; -use crate::admin::service::{parse_api_error, ApiError, ApiResponse, Empty}; +use crate::admin::service::{parse_api_error, ApiError, ApiResponse}; use crate::AdminState; -#[derive(Debug, Serialize)] -struct UpdateApplicationIdResponse { - data: Empty, -} - pub async fn handler( Extension(state): Extension>, Path(context_id): Path, @@ -37,7 +33,7 @@ pub async fn handler( match result { Ok(()) => ApiResponse { - payload: UpdateApplicationIdResponse { data: Empty {} }, + payload: UpdateContextApplicationResponse { data: Empty {} }, } .into_response(), Err(err) => err.into_response(), diff --git a/crates/server/src/admin/service.rs b/crates/server/src/admin/service.rs index bb5f0479a..832ea4bec 100644 --- a/crates/server/src/admin/service.rs +++ b/crates/server/src/admin/service.rs @@ -30,7 +30,7 @@ use crate::admin::handlers::applications::{ use crate::admin::handlers::challenge::request_challenge_handler; use crate::admin::handlers::context::{ create_context, delete_context, get_context, get_context_client_keys, get_context_identities, - get_context_storage, get_context_users, get_contexts, join_context, update_application_id, + get_context_storage, get_context_users, get_contexts, join_context, update_context_application, }; use crate::admin::handlers::did::fetch_did_handler; use crate::admin::handlers::root_keys::{create_root_key_handler, delete_auth_keys_handler}; @@ -159,7 +159,7 @@ pub(crate) fn setup( .route("/dev/contexts/join", post(join_context::handler)) .route( "/dev/contexts/:context_id/application", - post(update_application_id::handler), + post(update_context_application::handler), ) .route("/dev/applications", get(list_applications::handler)) .route("/dev/contexts/:context_id", get(get_context::handler)) From 0ee26fdff2498ff77f5262c5edff2dd221c10e6b Mon Sep 17 00:00:00 2001 From: Filip Bozic <70634661+fbozic@users.noreply.github.com> Date: Wed, 30 Oct 2024 21:48:54 +0100 Subject: [PATCH 4/7] chore: cargo format --- crates/meroctl/src/common.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/meroctl/src/common.rs b/crates/meroctl/src/common.rs index cf16adc71..7694f3663 100644 --- a/crates/meroctl/src/common.rs +++ b/crates/meroctl/src/common.rs @@ -6,7 +6,8 @@ use libp2p::identity::Keypair; use libp2p::multiaddr::Protocol; use libp2p::Multiaddr; use reqwest::{Client, Url}; -use serde::{de::DeserializeOwned, Serialize}; +use serde::de::DeserializeOwned; +use serde::Serialize; use crate::cli::ApiError; From 5c6646463ded7c542e4eff21724235f6eb72db74 Mon Sep 17 00:00:00 2001 From: Filip Bozic <70634661+fbozic@users.noreply.github.com> Date: Thu, 31 Oct 2024 10:05:55 +0100 Subject: [PATCH 5/7] feat(meroctl): replace all println with environment.output.write --- Cargo.toml | 4 -- crates/meroctl/src/cli/context/create.rs | 36 +++++++----------- crates/meroctl/src/cli/context/watch.rs | 47 +++++++++++++++++------- crates/meroctl/src/output.rs | 20 +++++++++- crates/server-primitives/src/ws.rs | 38 ------------------- crates/server/src/ws.rs | 9 +++-- crates/server/src/ws/subscribe.rs | 4 +- crates/server/src/ws/unsubscribe.rs | 4 +- 8 files changed, 77 insertions(+), 85 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 850b1892b..f6be9e2bf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -224,8 +224,6 @@ clone_on_ref_ptr = "deny" empty_enum_variants_with_brackets = "deny" empty_structs_with_brackets = "deny" error_impl_error = "deny" -exhaustive_enums = "deny" -exhaustive_structs = "deny" #expect_used = "deny" TODO: Enable as soon as possible float_cmp_const = "deny" fn_to_numeric_cast_any = "deny" @@ -237,8 +235,6 @@ lossy_float_literal = "deny" mem_forget = "deny" multiple_inherent_impl = "deny" #panic = "deny" TODO: Enable as soon as possible -print_stderr = "warn" -print_stdout = "warn" rc_mutex = "deny" renamed_function_params = "deny" try_err = "deny" diff --git a/crates/meroctl/src/cli/context/create.rs b/crates/meroctl/src/cli/context/create.rs index db369d162..e622f8d44 100644 --- a/crates/meroctl/src/cli/context/create.rs +++ b/crates/meroctl/src/cli/context/create.rs @@ -1,9 +1,3 @@ -#![allow( - clippy::print_stdout, - clippy::print_stderr, - reason = "Acceptable for CLI" -)] - use calimero_primitives::application::ApplicationId; use calimero_primitives::context::ContextId; use calimero_primitives::hash::Hash; @@ -25,7 +19,7 @@ use tokio::sync::mpsc; use crate::cli::Environment; use crate::common::{do_request, fetch_multiaddr, load_config, multiaddr_to_url, RequestType}; -use crate::output::Report; +use crate::output::{ErrorLine, InfoLine, Report}; #[derive(Debug, Parser)] #[command(about = "Create a new context")] @@ -201,13 +195,15 @@ async fn watch_app_and_update_context( watcher.watch(path.as_std_path(), RecursiveMode::NonRecursive)?; - println!("(i) Watching for changes to \"\x1b[36m{path}\x1b[0m\""); + environment + .output + .write(&InfoLine(&format!("Watching for changes to {}", path))); while let Some(event) = rx.recv().await { let event = match event { Ok(event) => event, Err(err) => { - eprintln!("\x1b[1mERROR\x1b[0m: {err:?}"); + environment.output.write(&ErrorLine(&format!("{err:?}"))); continue; } }; @@ -215,7 +211,9 @@ async fn watch_app_and_update_context( match event.kind { EventKind::Modify(ModifyKind::Data(_)) => {} EventKind::Remove(_) => { - eprintln!("\x1b[33mWARN\x1b[0m: file removed, ignoring.."); + environment + .output + .write(&ErrorLine("File removed, ignoring..")); continue; } EventKind::Any @@ -297,18 +295,12 @@ async fn install_app( metadata: Option>, keypair: &Keypair, ) -> EyreResult { - let install_url = multiaddr_to_url(base_multiaddr, "admin-api/dev/install-dev-application")?; - - let install_request = InstallDevApplicationRequest::new(path, metadata.unwrap_or_default()); - - let response: InstallApplicationResponse = do_request( - client, - install_url, - Some(install_request), - keypair, - RequestType::Post, - ) - .await?; + let url = multiaddr_to_url(base_multiaddr, "admin-api/dev/install-dev-application")?; + + let request = InstallDevApplicationRequest::new(path, metadata.unwrap_or_default()); + + let response: InstallApplicationResponse = + do_request(client, url, Some(request), keypair, RequestType::Post).await?; environment.output.write(&response); diff --git a/crates/meroctl/src/cli/context/watch.rs b/crates/meroctl/src/cli/context/watch.rs index 67e9c023a..c64b48d56 100644 --- a/crates/meroctl/src/cli/context/watch.rs +++ b/crates/meroctl/src/cli/context/watch.rs @@ -1,12 +1,14 @@ use calimero_primitives::context::ContextId; -use calimero_server_primitives::ws::{RequestPayload, SubscribeRequest}; +use calimero_server_primitives::ws::{Request, RequestPayload, Response, SubscribeRequest}; use clap::Parser; use eyre::Result as EyreResult; use futures_util::{SinkExt, StreamExt}; use tokio_tungstenite::connect_async; +use tokio_tungstenite::tungstenite::Message as WsMessage; use crate::cli::Environment; use crate::common::{fetch_multiaddr, load_config, multiaddr_to_url}; +use crate::output::{InfoLine, Report}; #[derive(Debug, Parser)] #[command(about = "Watch events from a context")] @@ -16,6 +18,13 @@ pub struct WatchCommand { pub context_id: ContextId, } +impl Report for Response { + fn report(&self) { + println!("id: {:?}", self.id); + println!("payload: {:?}", self.body); + } +} + impl WatchCommand { pub async fn run(self, environment: &Environment) -> EyreResult<()> { let config = load_config(&environment.args.home, &environment.args.node_name)?; @@ -24,28 +33,38 @@ impl WatchCommand { url.set_scheme("ws") .map_err(|_| eyre::eyre!("Failed to set URL scheme"))?; - println!("Connecting to WebSocket at {}", url); + environment + .output + .write(&InfoLine(&format!("Connecting to WebSocket at {url}"))); let (ws_stream, _) = connect_async(url.as_str()).await?; - let (mut write, mut read) = ws_stream.split(); - // Send subscribe message - let subscribe_request = - RequestPayload::Subscribe(SubscribeRequest::new(vec![self.context_id])); - let subscribe_msg = serde_json::to_string(&subscribe_request)?; - write - .send(tokio_tungstenite::tungstenite::Message::Text(subscribe_msg)) - .await?; + let subscribe_request = RequestPayload::Subscribe(SubscribeRequest { + context_ids: vec![self.context_id], + }); + let request = Request { + id: None, + payload: serde_json::to_value(&subscribe_request)?, + }; + + let subscribe_msg = serde_json::to_string(&request)?; + write.send(WsMessage::Text(subscribe_msg)).await?; - println!("Subscribed to context {}", self.context_id); - println!("Streaming events (press Ctrl+C to stop):"); + environment.output.write(&InfoLine(&format!( + "Subscribed to context {}", + self.context_id + ))); + environment.output.write(&InfoLine(&format!( + "Streaming events (press Ctrl+C to stop):" + ))); while let Some(message) = read.next().await { match message { Ok(msg) => { - if let tokio_tungstenite::tungstenite::Message::Text(text) = msg { - println!("{}", text); + if let WsMessage::Text(text) = msg { + let response = serde_json::from_str::(&text)?; + environment.output.write(&response); } } Err(e) => eprintln!("Error receiving message: {}", e), diff --git a/crates/meroctl/src/output.rs b/crates/meroctl/src/output.rs index c68066a61..1c2f4d742 100644 --- a/crates/meroctl/src/output.rs +++ b/crates/meroctl/src/output.rs @@ -1,7 +1,7 @@ use clap::ValueEnum; use serde::Serialize; -#[derive(Clone, Copy, Debug, Default, ValueEnum, Serialize)] +#[derive(Clone, Copy, Debug, Default, ValueEnum)] pub enum Format { Json, #[default] @@ -34,3 +34,21 @@ impl Output { } } } + +#[derive(Clone, Debug, Serialize)] +pub struct InfoLine<'a>(pub &'a str); + +impl Report for InfoLine<'_> { + fn report(&self) { + println!("{}", self.0); + } +} + +#[derive(Clone, Debug, Serialize)] +pub struct ErrorLine<'a>(pub &'a str); + +impl Report for ErrorLine<'_> { + fn report(&self) { + println!("[ERROR] {}", self.0); + } +} diff --git a/crates/server-primitives/src/ws.rs b/crates/server-primitives/src/ws.rs index 132002cba..d3878800d 100644 --- a/crates/server-primitives/src/ws.rs +++ b/crates/server-primitives/src/ws.rs @@ -11,7 +11,6 @@ pub type RequestId = u64; // **************************** request ******************************* #[derive(Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] -#[non_exhaustive] pub struct Request

{ pub id: Option, #[serde(flatten)] @@ -20,7 +19,6 @@ pub struct Request

{ #[derive(Debug, Deserialize, Serialize)] #[serde(tag = "method", content = "params", rename_all = "snake_case")] -#[non_exhaustive] pub enum RequestPayload { Subscribe(SubscribeRequest), Unsubscribe(UnsubscribeRequest), @@ -30,20 +28,12 @@ pub enum RequestPayload { // **************************** response ******************************* #[derive(Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] -#[non_exhaustive] pub struct Response { pub id: Option, #[serde(flatten)] pub body: ResponseBody, } -impl Response { - #[must_use] - pub const fn new(id: Option, body: ResponseBody) -> Self { - Self { id, body } - } -} - #[derive(Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] #[expect( @@ -57,7 +47,6 @@ pub enum ResponseBody { #[derive(Debug, Deserialize, Serialize)] #[serde(untagged)] -#[non_exhaustive] pub enum ResponseBodyError { ServerError(ServerResponseError), HandlerError(Value), @@ -65,7 +54,6 @@ pub enum ResponseBodyError { #[derive(Debug, Deserialize, Serialize)] #[serde(tag = "type", content = "data")] -#[non_exhaustive] pub enum ServerResponseError { ParseError(String), InternalError { @@ -78,58 +66,32 @@ pub enum ServerResponseError { // **************************** subscribe method ******************************* #[derive(Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] -#[non_exhaustive] pub struct SubscribeRequest { pub context_ids: Vec, } -impl SubscribeRequest { - #[must_use] - pub const fn new(context_ids: Vec) -> Self { - Self { context_ids } - } -} - #[derive(Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] -#[non_exhaustive] pub struct SubscribeResponse { pub context_ids: Vec, } - -impl SubscribeResponse { - #[must_use] - pub const fn new(context_ids: Vec) -> Self { - Self { context_ids } - } -} // ************************************************************************* // **************************** unsubscribe method ******************************* #[derive(Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] -#[non_exhaustive] pub struct UnsubscribeRequest { pub context_ids: Vec, } #[derive(Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] -#[non_exhaustive] pub struct UnsubscribeResponse { pub context_ids: Vec, } - -impl UnsubscribeResponse { - #[must_use] - pub const fn new(context_ids: Vec) -> Self { - Self { context_ids } - } -} // ************************************************************************* #[derive(Debug)] -#[non_exhaustive] pub enum Command { Close(u16, String), Send(Response), diff --git a/crates/server/src/ws.rs b/crates/server/src/ws.rs index 21fc89f23..9fa52dcb6 100644 --- a/crates/server/src/ws.rs +++ b/crates/server/src/ws.rs @@ -213,7 +213,7 @@ async fn handle_node_events( )), }; - let response = Response::new(None, body); + let response = Response { id: None, body }; if let Err(err) = command_sender.send(Command::Send(response)).await { error!( @@ -263,7 +263,6 @@ async fn handle_commands( error!(%connection_id, %err, "Failed to send Message::Text"); } } - _ => unreachable!("Unexpected Command"), } } } @@ -302,7 +301,6 @@ async fn handle_text_message( .handle(Arc::clone(&state), connection_state.clone()) .await .to_res_body(), - _ => unreachable!("Unsupported WebSocket method"), }, Err(err) => { error!(%connection_id, %err, "Failed to deserialize RequestPayload"); @@ -315,7 +313,10 @@ async fn handle_text_message( if let Err(err) = connection_state .commands - .send(Command::Send(Response::new(message.id, body))) + .send(Command::Send(Response { + id: message.id, + body, + })) .await { error!( diff --git a/crates/server/src/ws/subscribe.rs b/crates/server/src/ws/subscribe.rs index b800d9887..50e93cb03 100644 --- a/crates/server/src/ws/subscribe.rs +++ b/crates/server/src/ws/subscribe.rs @@ -18,5 +18,7 @@ async fn handle( let _ = inner.subscriptions.insert(*id); }); - Ok(SubscribeResponse::new(request.context_ids)) + Ok(SubscribeResponse { + context_ids: request.context_ids, + }) } diff --git a/crates/server/src/ws/unsubscribe.rs b/crates/server/src/ws/unsubscribe.rs index cca58cdcc..b5ae297ef 100644 --- a/crates/server/src/ws/unsubscribe.rs +++ b/crates/server/src/ws/unsubscribe.rs @@ -18,5 +18,7 @@ async fn handle( let _ = inner.subscriptions.remove(id); }); - Ok(UnsubscribeResponse::new(request.context_ids)) + Ok(UnsubscribeResponse { + context_ids: request.context_ids, + }) } From fc6fb1e447114de74702855369f473241b2debee Mon Sep 17 00:00:00 2001 From: Filip Bozic <70634661+fbozic@users.noreply.github.com> Date: Thu, 31 Oct 2024 11:39:17 +0100 Subject: [PATCH 6/7] feat(meroctl): improve error Reports and line Reports --- crates/meroctl/src/cli.rs | 4 ++-- crates/meroctl/src/output.rs | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/crates/meroctl/src/cli.rs b/crates/meroctl/src/cli.rs index 8d7a97216..ce6a5cd37 100644 --- a/crates/meroctl/src/cli.rs +++ b/crates/meroctl/src/cli.rs @@ -127,7 +127,7 @@ impl Report for CliError { } #[derive(Debug, Serialize, ThisError)] -#[error("ApiError")] +#[error("{status_code}: {message}")] pub struct ApiError { pub status_code: u16, pub message: String, @@ -137,5 +137,5 @@ fn serialize_eyre_report(report: &EyreReport, serializer: S) -> Result(pub &'a str); impl Report for InfoLine<'_> { fn report(&self) { - println!("{}", self.0); + println!("{} {}", "[INFO]".green(), self.0); } } @@ -49,6 +50,6 @@ pub struct ErrorLine<'a>(pub &'a str); impl Report for ErrorLine<'_> { fn report(&self) { - println!("[ERROR] {}", self.0); + println!("{} {}", "[ERROR]".red(), self.0); } } From 8aee3b6aa6d8480e1618b41ccfdf667e27e8749b Mon Sep 17 00:00:00 2001 From: Filip Bozic <70634661+fbozic@users.noreply.github.com> Date: Thu, 31 Oct 2024 12:16:30 +0100 Subject: [PATCH 7/7] fix(meroctl): return CliError after it is logged to main --- crates/meroctl/src/cli.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/meroctl/src/cli.rs b/crates/meroctl/src/cli.rs index ce6a5cd37..87351983d 100644 --- a/crates/meroctl/src/cli.rs +++ b/crates/meroctl/src/cli.rs @@ -92,6 +92,7 @@ impl RootCommand { Err(err) => CliError::Other(err), }; environment.output.write(&err); + return Err(err); } return Ok(());