From 017bce883a2f42bd15fdff09e441372d788725d1 Mon Sep 17 00:00:00 2001 From: Berend Sliedrecht Date: Fri, 10 Dec 2021 15:57:56 +0100 Subject: [PATCH 1/9] feat: feature discovery Signed-off-by: Berend Sliedrecht --- cli.yaml | 3 +++ src/agent/agent.rs | 5 +++-- src/agent/http_agent.rs | 49 +++++++++++++++-------------------------- src/cli/feature.rs | 10 +++++++++ src/cli/invite.rs | 2 +- src/cli/mod.rs | 1 + src/error.rs | 12 +++------- src/main.rs | 9 ++++++-- src/typing.rs | 6 +++++ src/utils/http.rs | 43 +++++++++++++++++++++++++++++------- 10 files changed, 87 insertions(+), 53 deletions(-) create mode 100644 src/cli/feature.rs diff --git a/cli.yaml b/cli.yaml index 59cfc469..5fd51cdf 100644 --- a/cli.yaml +++ b/cli.yaml @@ -44,3 +44,6 @@ subcommands: short: l long: alias takes_value: true + - features: + about: discover features subcommand + version: "0.1.0" diff --git a/src/agent/agent.rs b/src/agent/agent.rs index 47574646..3d53b177 100644 --- a/src/agent/agent.rs +++ b/src/agent/agent.rs @@ -1,4 +1,4 @@ -use crate::typing::{Connection, Connections, Invitation, InviteConfiguration, Result}; +use crate::typing::{Connection, Connections, Feature, Invitation, InviteConfiguration, Result}; use async_trait::async_trait; #[async_trait] @@ -6,10 +6,11 @@ pub trait Agent { async fn get_connections(&self) -> Connections; async fn get_connection_by_id(&self, id: String) -> Connection; async fn create_invitation(&self, config: &InviteConfiguration<'_>) -> Invitation; + async fn discover_features(&self) -> Feature; } #[async_trait] pub trait HttpAgentExtended: Agent { fn new(endpoint: &str) -> Self; - async fn check_endpoint(&self) -> Result; + async fn check_endpoint(&self) -> Result<()>; } diff --git a/src/agent/http_agent.rs b/src/agent/http_agent.rs index 01350fa6..d3e8878a 100644 --- a/src/agent/http_agent.rs +++ b/src/agent/http_agent.rs @@ -1,6 +1,6 @@ use crate::agent::agent::{Agent, HttpAgentExtended}; use crate::error; -use crate::typing; +use crate::typing::{self, Connection, Connections, Feature}; use crate::utils::http; use async_trait::async_trait; use reqwest::Url; @@ -39,6 +39,12 @@ impl Endpoint { .join("create-invitation") .expect(&format!("Could not join on create-invitation").to_string()) } + fn discover_features(agent: &HttpAgent) -> Url { + reqwest::Url::parse(&agent.url) + .expect(&format!("Could not join on {}", agent.url).to_string()) + .join("features") + .expect(&format!("Could not join on features").to_string()) + } } #[async_trait] @@ -49,12 +55,13 @@ impl HttpAgentExtended for HttpAgent { } } - async fn check_endpoint(&self) -> typing::Result { + async fn check_endpoint(&self) -> typing::Result<()> { match reqwest::get(Endpoint::connections(&self)).await { Ok(res) => { if res.status().is_success() { - return Ok(true); + return Ok(()); } + Err(error::Error::InvalidUrl) } Err(_) => Err(error::Error::InvalidUrl), @@ -64,24 +71,12 @@ impl HttpAgentExtended for HttpAgent { #[async_trait] impl Agent for HttpAgent { - async fn get_connections(&self) -> typing::Connections { - match http::get(Endpoint::connections(&self), None).await { - Ok(res) => match res.json().await { - Ok(parsed) => parsed, - Err(_) => error::throw(error::Error::ServerResponseParseError), - }, - Err(_) => error::throw(error::Error::ConnectionsUnretrieveable), - } + async fn get_connections(&self) -> Connections { + http::get::(Endpoint::connections(&self), None).await } async fn get_connection_by_id(&self, id: String) -> typing::Connection { - match http::get(Endpoint::get_connection_by_id(&self, id), None).await { - Ok(res) => match res.json().await { - Ok(parsed) => parsed, - Err(_) => error::throw(error::Error::ServerResponseParseError), - }, - Err(_) => error::throw(error::Error::ConnectionDoesNotExist), - } + http::get::(Endpoint::get_connection_by_id(&self, id), None).await } async fn create_invitation( @@ -115,18 +110,10 @@ impl Agent for HttpAgent { .and_then(|alias| Some(query.push(("alias", alias.to_string())))); } - // TODO: the post call should already check the status and try to parse it. - match http::post(Endpoint::create_invitation(&self), query, body).await { - Ok(res) => { - if res.status().is_success() { - return match res.json().await { - Ok(parsed) => parsed, - Err(_) => error::throw(error::Error::ServerResponseParseError), - }; - } - panic!("{:?}", res); - } - Err(_) => error::throw(error::Error::CannotCreateInvitation), - } + http::post(Endpoint::create_invitation(&self), query, body).await + } + + async fn discover_features(&self) -> Feature { + http::get::(Endpoint::discover_features(&self), None).await } } diff --git a/src/cli/feature.rs b/src/cli/feature.rs new file mode 100644 index 00000000..fa81837c --- /dev/null +++ b/src/cli/feature.rs @@ -0,0 +1,10 @@ +use crate::agent::agent::Agent; +use crate::utils::logger::Log; +use crate::HttpAgent; + +pub async fn run(agent: &HttpAgent) { + let features = agent.discover_features().await; + for (_, item) in features.results.iter().enumerate() { + Log::log(item.0); + } +} diff --git a/src/cli/invite.rs b/src/cli/invite.rs index e557253e..04d5333b 100644 --- a/src/cli/invite.rs +++ b/src/cli/invite.rs @@ -4,7 +4,7 @@ use crate::utils::logger::Log; use crate::utils::qr; use crate::HttpAgent; -pub async fn run(agent: HttpAgent, config: typing::InviteConfiguration<'_>) { +pub async fn run(agent: &HttpAgent, config: typing::InviteConfiguration<'_>) { let invitation = agent.create_invitation(&config).await; if config.qr { diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 31109792..a6e38c4b 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -1 +1,2 @@ +pub mod feature; pub mod invite; diff --git a/src/error.rs b/src/error.rs index bc051f14..f8786897 100644 --- a/src/error.rs +++ b/src/error.rs @@ -5,9 +5,7 @@ pub enum Error { InvalidEndpoint, InvalidUrl, ServerResponseParseError, - CannotCreateInvitation, - ConnectionsUnretrieveable, - ConnectionDoesNotExist, + InternalServerError, } // Error handler (Should not panic but print a custom error and exit) @@ -19,11 +17,7 @@ pub fn throw(error: Error) -> ! { Error::InvalidUrl => Log::error("Invalid Url"), // Could not parse the response from the server Error::ServerResponseParseError => Log::error("Unable to parse response from server"), - // The connection does not exist on the agent - Error::ConnectionDoesNotExist => Log::error("Connection does not exist"), - // The connection list is unretrieveable (Could this even happen?) - Error::ConnectionsUnretrieveable => Log::error("Connection is unretrieveable"), - // Could not create an invitation - Error::CannotCreateInvitation => Log::error("Could not create an invitation"), + // The server did not respond with a success code + Error::InternalServerError => Log::error("Internal Server Error"), } } diff --git a/src/main.rs b/src/main.rs index 80af99b0..39bca02b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -34,7 +34,12 @@ async fn main() { Ok(_) => true, }; - // Matches the `agent` subcommand + // Matches the `feature` subcommand + if let Some(_) = matches.subcommand_matches("features") { + cli::feature::run(&agent).await + } + + // Matches the `invite` subcommand if let Some(matches_agent) = matches.subcommand_matches("invite") { let auto_accept = matches_agent.is_present("auto-accept"); let multi_use = matches_agent.is_present("multi-use"); @@ -51,6 +56,6 @@ async fn main() { }; // create agent and convert config - cli::invite::run(agent, config).await + cli::invite::run(&agent, config).await } } diff --git a/src/typing.rs b/src/typing.rs index bc6ca997..8a787680 100644 --- a/src/typing.rs +++ b/src/typing.rs @@ -1,5 +1,6 @@ use super::error::Error; use serde::{Deserialize, Serialize}; +use serde_json::{Map, Value}; pub type Result = std::result::Result; @@ -60,3 +61,8 @@ pub struct MetaInvitation { pub recipient_keys: Vec, pub label: String, } + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Feature { + pub results: Map, +} diff --git a/src/utils/http.rs b/src/utils/http.rs index d32d96a0..6494b986 100644 --- a/src/utils/http.rs +++ b/src/utils/http.rs @@ -1,29 +1,56 @@ use std::collections::HashMap; use reqwest::{Client, Url}; +use serde::de::DeserializeOwned; + +use crate::error; // Handle calling of any endpoint with get -pub async fn get( - url: Url, - query: Option>, -) -> Result { +pub async fn get(url: Url, query: Option>) -> T { let client = match query { Some(q) => Client::new().get(url).query(&q), None => Client::new().get(url), }; - client.send().await + + let response = client.send().await; + + match response { + Ok(res) => { + if res.status().is_success() { + return match res.json().await { + Ok(parsed) => parsed, + Err(_) => error::throw(error::Error::ServerResponseParseError), + }; + } + error::throw(error::Error::InternalServerError) + } + Err(_) => error::throw(error::Error::InternalServerError), + } } // Handle calling of any endpoint with post -pub async fn post( +pub async fn post( url: Url, query: Vec<(&str, String)>, body: Option>>, -) -> Result { +) -> T { let client = Client::new().post(url).query(&query); - match body { + let response = match body { Some(b) => client.json(&b).send().await, None => client.send().await, + }; + + match response { + Ok(res) => { + if res.status().is_success() { + return match res.json().await { + Ok(parsed) => parsed, + Err(_) => error::throw(error::Error::ServerResponseParseError), + }; + } + error::throw(error::Error::InternalServerError) + } + Err(_) => error::throw(error::Error::InternalServerError), } } From cc827cda07595d622d33a9d990a172dee02f56a0 Mon Sep 17 00:00:00 2001 From: Berend Sliedrecht Date: Fri, 10 Dec 2021 16:05:32 +0100 Subject: [PATCH 2/9] refactor: minor clean up Signed-off-by: Berend Sliedrecht --- src/agent/agent.rs | 4 ++-- src/agent/http_agent.rs | 15 +++------------ src/error.rs | 3 --- src/main.rs | 8 +++----- src/typing.rs | 3 --- src/utils/http.rs | 14 +++++++------- 6 files changed, 15 insertions(+), 32 deletions(-) diff --git a/src/agent/agent.rs b/src/agent/agent.rs index 3d53b177..ca211153 100644 --- a/src/agent/agent.rs +++ b/src/agent/agent.rs @@ -1,4 +1,4 @@ -use crate::typing::{Connection, Connections, Feature, Invitation, InviteConfiguration, Result}; +use crate::typing::{Connection, Connections, Feature, Invitation, InviteConfiguration}; use async_trait::async_trait; #[async_trait] @@ -12,5 +12,5 @@ pub trait Agent { #[async_trait] pub trait HttpAgentExtended: Agent { fn new(endpoint: &str) -> Self; - async fn check_endpoint(&self) -> Result<()>; + async fn check_endpoint(&self) -> (); } diff --git a/src/agent/http_agent.rs b/src/agent/http_agent.rs index d3e8878a..ffcfc093 100644 --- a/src/agent/http_agent.rs +++ b/src/agent/http_agent.rs @@ -1,5 +1,4 @@ use crate::agent::agent::{Agent, HttpAgentExtended}; -use crate::error; use crate::typing::{self, Connection, Connections, Feature}; use crate::utils::http; use async_trait::async_trait; @@ -55,17 +54,9 @@ impl HttpAgentExtended for HttpAgent { } } - async fn check_endpoint(&self) -> typing::Result<()> { - match reqwest::get(Endpoint::connections(&self)).await { - Ok(res) => { - if res.status().is_success() { - return Ok(()); - } - - Err(error::Error::InvalidUrl) - } - Err(_) => Err(error::Error::InvalidUrl), - } + async fn check_endpoint(&self) -> () { + http::get::(Endpoint::connections(&self), None).await; + () } } diff --git a/src/error.rs b/src/error.rs index f8786897..fe95eddc 100644 --- a/src/error.rs +++ b/src/error.rs @@ -3,7 +3,6 @@ use crate::utils::logger::Log; // Error types pub enum Error { InvalidEndpoint, - InvalidUrl, ServerResponseParseError, InternalServerError, } @@ -13,8 +12,6 @@ pub fn throw(error: Error) -> ! { match error { // The endpoint in the configuration file is invalid Error::InvalidEndpoint => Log::error("Invalid Endpoint"), - // The url created from the base + endpoint is invalid - Error::InvalidUrl => Log::error("Invalid Url"), // Could not parse the response from the server Error::ServerResponseParseError => Log::error("Unable to parse response from server"), // The server did not respond with a success code diff --git a/src/main.rs b/src/main.rs index 39bca02b..3b8a539e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,6 +6,7 @@ extern crate clap; use crate::agent::agent::HttpAgentExtended; use crate::agent::http_agent::HttpAgent; +use crate::error::{throw, Error}; use clap::App; mod agent; @@ -26,13 +27,10 @@ async fn main() { // create an httpAgent when you supply an endpoint let agent = match matches.value_of("endpoint") { Some(endpoint) => HttpAgent::new(endpoint), - None => error::throw(error::Error::InvalidEndpoint), + None => throw(Error::InvalidEndpoint), }; - match agent.check_endpoint().await { - Err(e) => error::throw(e), - Ok(_) => true, - }; + agent.check_endpoint().await; // Matches the `feature` subcommand if let Some(_) = matches.subcommand_matches("features") { diff --git a/src/typing.rs b/src/typing.rs index 8a787680..881fdd36 100644 --- a/src/typing.rs +++ b/src/typing.rs @@ -1,9 +1,6 @@ -use super::error::Error; use serde::{Deserialize, Serialize}; use serde_json::{Map, Value}; -pub type Result = std::result::Result; - pub struct InviteConfiguration<'a> { pub auto_accept: bool, pub multi_use: bool, diff --git a/src/utils/http.rs b/src/utils/http.rs index 6494b986..9b251622 100644 --- a/src/utils/http.rs +++ b/src/utils/http.rs @@ -3,7 +3,7 @@ use std::collections::HashMap; use reqwest::{Client, Url}; use serde::de::DeserializeOwned; -use crate::error; +use crate::error::{throw, Error}; // Handle calling of any endpoint with get pub async fn get(url: Url, query: Option>) -> T { @@ -19,12 +19,12 @@ pub async fn get(url: Url, query: Option> if res.status().is_success() { return match res.json().await { Ok(parsed) => parsed, - Err(_) => error::throw(error::Error::ServerResponseParseError), + Err(_) => throw(Error::ServerResponseParseError), }; } - error::throw(error::Error::InternalServerError) + throw(Error::InternalServerError) } - Err(_) => error::throw(error::Error::InternalServerError), + Err(_) => throw(Error::InternalServerError), } } @@ -46,11 +46,11 @@ pub async fn post( if res.status().is_success() { return match res.json().await { Ok(parsed) => parsed, - Err(_) => error::throw(error::Error::ServerResponseParseError), + Err(_) => throw(Error::ServerResponseParseError), }; } - error::throw(error::Error::InternalServerError) + throw(Error::InternalServerError) } - Err(_) => error::throw(error::Error::InternalServerError), + Err(_) => throw(Error::InternalServerError), } } From eeae0ed17ce7f94e21bb917ede70fbd269fb36f6 Mon Sep 17 00:00:00 2001 From: Berend Sliedrecht Date: Sat, 11 Dec 2021 16:18:51 +0100 Subject: [PATCH 3/9] feat: connections subcommand Signed-off-by: Berend Sliedrecht --- Cargo.toml | 3 + cli.yaml | 20 +++++-- src/agent/{agent.rs => agents.rs} | 10 ++-- src/agent/http_agent.rs | 86 ++++++++++++++--------------- src/agent/mod.rs | 2 +- src/cli/connections.rs | 31 +++++++++++ src/cli/{feature.rs => features.rs} | 5 +- src/cli/invite.rs | 7 +-- src/cli/mod.rs | 3 +- src/main.rs | 46 +++++++++++---- src/typing.rs | 7 ++- src/utils/http.rs | 7 +-- src/utils/qr.rs | 2 +- 13 files changed, 149 insertions(+), 80 deletions(-) rename src/agent/{agent.rs => agents.rs} (50%) create mode 100644 src/cli/connections.rs rename src/cli/{feature.rs => features.rs} (65%) diff --git a/Cargo.toml b/Cargo.toml index 86074b12..e48791b3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,9 @@ [package] name = "aries-cloudagent-controller-ferris" version = "0.1.0" +authors = ["Animo Solutions"] +description = "A simple Aries Cloudagent Controller" +homepage = "animo.id" edition = "2018" [[bin]] diff --git a/cli.yaml b/cli.yaml index 5fd51cdf..c26cd001 100644 --- a/cli.yaml +++ b/cli.yaml @@ -1,9 +1,5 @@ -# General information name: Aries Cloudagent Controller Ferris -version: "0.1.0" -author: Animo Solutions -about: An Aries Cloudagent Controller to interact with an Aries instance for quick data manipulation - + # Application settings settings: - ArgRequiredElseHelp @@ -44,6 +40,20 @@ subcommands: short: l long: alias takes_value: true + - connections: + about: connections subcommand + version: "0.1.0" + args: + - id: + help: connection id + short: i + long: id + takes_value: true + - alias: + help: filter on alias + short: a + long: alias + takes_value: true - features: about: discover features subcommand version: "0.1.0" diff --git a/src/agent/agent.rs b/src/agent/agents.rs similarity index 50% rename from src/agent/agent.rs rename to src/agent/agents.rs index ca211153..f70c2681 100644 --- a/src/agent/agent.rs +++ b/src/agent/agents.rs @@ -1,16 +1,16 @@ -use crate::typing::{Connection, Connections, Feature, Invitation, InviteConfiguration}; +use crate::typing::{Connection, Connections, Feature, Invitation, InvitationConfig}; use async_trait::async_trait; #[async_trait] pub trait Agent { - async fn get_connections(&self) -> Connections; - async fn get_connection_by_id(&self, id: String) -> Connection; - async fn create_invitation(&self, config: &InviteConfiguration<'_>) -> Invitation; + async fn get_connections(&self, filter: Option<&str>) -> Connections; + async fn get_connection_by_id(&self, id: &str) -> Connection; + async fn create_invitation(&self, config: &InvitationConfig<'_>) -> Invitation; async fn discover_features(&self) -> Feature; } #[async_trait] -pub trait HttpAgentExtended: Agent { +pub trait HttpAgentExtended { fn new(endpoint: &str) -> Self; async fn check_endpoint(&self) -> (); } diff --git a/src/agent/http_agent.rs b/src/agent/http_agent.rs index ffcfc093..c7e5a162 100644 --- a/src/agent/http_agent.rs +++ b/src/agent/http_agent.rs @@ -1,9 +1,9 @@ -use crate::agent::agent::{Agent, HttpAgentExtended}; +use crate::agent::agents::{Agent, HttpAgentExtended}; use crate::typing::{self, Connection, Connections, Feature}; use crate::utils::http; use async_trait::async_trait; use reqwest::Url; -use std::collections::HashMap; +use serde_json::json; #[derive(Debug, Clone)] pub struct HttpAgent { @@ -16,33 +16,33 @@ struct Endpoint; // Default value for every endpoint // TODO: Not the most efficient mehtod (creates a new instance for every function call) impl Endpoint { - fn connections(agent: &HttpAgent) -> Url { - reqwest::Url::parse(&agent.url) - .expect(&format!("Could not join on {}", agent.url).to_string()) + fn connections(url: &str) -> Url { + reqwest::Url::parse(url) + .expect(&format!("Could not join on {}", url)) .join("connections") - .expect(&format!("Could not join on connections").to_string()) + .expect(&format!("Could not join on connections")) } - fn get_connection_by_id(agent: &HttpAgent, id: String) -> Url { - reqwest::Url::parse(&agent.url) - .expect(&format!("Could not join on {}", agent.url).to_string()) + fn get_connection_by_id(url: &str, id: &str) -> Url { + reqwest::Url::parse(url) + .expect(&format!("Could not join on {}", url)) .join("connections/") - .expect(&format!("Could not join on connections/").to_string()) - .join(&id.to_string()) - .expect(&format!("Could not join on {}", id).to_string()) + .expect(&format!("Could not join on connections/")) + .join(id) + .expect(&format!("Could not join on {}", id)) } - fn create_invitation(agent: &HttpAgent) -> Url { - reqwest::Url::parse(&agent.url) - .expect(&format!("Could not join on {}", agent.url).to_string()) + fn create_invitation(url: &str) -> Url { + reqwest::Url::parse(url) + .expect(&format!("Could not join on {}", url)) .join("connections/") - .expect(&format!("Could not join on connections/").to_string()) + .expect(&format!("Could not join on connections/")) .join("create-invitation") - .expect(&format!("Could not join on create-invitation").to_string()) + .expect(&format!("Could not join on create-invitation")) } - fn discover_features(agent: &HttpAgent) -> Url { - reqwest::Url::parse(&agent.url) - .expect(&format!("Could not join on {}", agent.url).to_string()) + fn discover_features(url: &str) -> Url { + reqwest::Url::parse(url) + .expect(&format!("Could not join on {}", url)) .join("features") - .expect(&format!("Could not join on features").to_string()) + .expect(&format!("Could not join on features")) } } @@ -55,25 +55,27 @@ impl HttpAgentExtended for HttpAgent { } async fn check_endpoint(&self) -> () { - http::get::(Endpoint::connections(&self), None).await; - () + http::get::(Endpoint::connections(&self.url), None).await; } } #[async_trait] impl Agent for HttpAgent { - async fn get_connections(&self) -> Connections { - http::get::(Endpoint::connections(&self), None).await + async fn get_connections(&self, filter: Option<&str>) -> Connections { + let mut query: Vec<(&str, String)> = vec![]; + + if let Some(alias) = filter { + query.push(("alias", alias.to_string())); + } + + http::get::(Endpoint::connections(&self.url), Some(query)).await } - async fn get_connection_by_id(&self, id: String) -> typing::Connection { - http::get::(Endpoint::get_connection_by_id(&self, id), None).await + async fn get_connection_by_id(&self, id: &str) -> typing::Connection { + http::get::(Endpoint::get_connection_by_id(&self.url, id), None).await } - async fn create_invitation( - &self, - config: &typing::InviteConfiguration<'_>, - ) -> typing::Invitation { + async fn create_invitation(&self, config: &typing::InvitationConfig<'_>) -> typing::Invitation { let mut query: Vec<(&str, String)> = vec![]; let mut body = None; @@ -82,13 +84,11 @@ impl Agent for HttpAgent { query.push(("auto_accept", true.to_string())); query.push(("alias", String::from("toolbox"))); - let mut a = HashMap::new(); - let mut b = HashMap::new(); - - b.insert("group", "admin"); - a.insert("metadata", b); - - body = Some(a); + body = Some(json!({ + "metadata": { + "group": "admin" + } + })); } else { let multi_use = ("multi_use", config.multi_use.to_string()); let auto_accept = ("auto_accept", config.auto_accept.to_string()); @@ -96,15 +96,15 @@ impl Agent for HttpAgent { query.push(multi_use); query.push(auto_accept); - config - .alias - .and_then(|alias| Some(query.push(("alias", alias.to_string())))); + if let Some(alias) = config.alias { + query.push(("alias", alias.to_string())); + } } - http::post(Endpoint::create_invitation(&self), query, body).await + http::post(Endpoint::create_invitation(&self.url), query, body).await } async fn discover_features(&self) -> Feature { - http::get::(Endpoint::discover_features(&self), None).await + http::get::(Endpoint::discover_features(&self.url), None).await } } diff --git a/src/agent/mod.rs b/src/agent/mod.rs index c0f68376..d6fc78b5 100644 --- a/src/agent/mod.rs +++ b/src/agent/mod.rs @@ -1,2 +1,2 @@ -pub mod agent; +pub mod agents; pub mod http_agent; diff --git a/src/cli/connections.rs b/src/cli/connections.rs new file mode 100644 index 00000000..b7233e85 --- /dev/null +++ b/src/cli/connections.rs @@ -0,0 +1,31 @@ +use crate::agent::agents::Agent; +use crate::typing::ConnectionsConfig; +use crate::utils::logger::Log; +use serde::Serialize; + +pub async fn run(agent: &dyn Agent, config: ConnectionsConfig<'_>) { + match config.id { + Some(i) => { + let connection = agent.get_connection_by_id(i).await; + + let buf = Vec::new(); + let formatter = serde_json::ser::PrettyFormatter::with_indent(b" "); + let mut ser = serde_json::Serializer::with_formatter(buf, formatter); + + connection.serialize(&mut ser).unwrap(); + + Log::log(&String::from_utf8(ser.into_inner()).unwrap()); + } + None => { + let connections = agent.get_connections(config.alias).await.results; + + let buf = Vec::new(); + let formatter = serde_json::ser::PrettyFormatter::with_indent(b" "); + let mut ser = serde_json::Serializer::with_formatter(buf, formatter); + + connections.serialize(&mut ser).unwrap(); + + Log::log(&String::from_utf8(ser.into_inner()).unwrap()); + } + }; +} diff --git a/src/cli/feature.rs b/src/cli/features.rs similarity index 65% rename from src/cli/feature.rs rename to src/cli/features.rs index fa81837c..18e8e6c6 100644 --- a/src/cli/feature.rs +++ b/src/cli/features.rs @@ -1,8 +1,7 @@ -use crate::agent::agent::Agent; +use crate::agent::agents::Agent; use crate::utils::logger::Log; -use crate::HttpAgent; -pub async fn run(agent: &HttpAgent) { +pub async fn run(agent: &dyn Agent) { let features = agent.discover_features().await; for (_, item) in features.results.iter().enumerate() { Log::log(item.0); diff --git a/src/cli/invite.rs b/src/cli/invite.rs index 04d5333b..0dd29a1d 100644 --- a/src/cli/invite.rs +++ b/src/cli/invite.rs @@ -1,10 +1,9 @@ -use crate::agent::agent::Agent; -use crate::typing; +use crate::agent::agents::Agent; +use crate::typing::InvitationConfig; use crate::utils::logger::Log; use crate::utils::qr; -use crate::HttpAgent; -pub async fn run(agent: &HttpAgent, config: typing::InviteConfiguration<'_>) { +pub async fn run(agent: &dyn Agent, config: InvitationConfig<'_>) { let invitation = agent.create_invitation(&config).await; if config.qr { diff --git a/src/cli/mod.rs b/src/cli/mod.rs index a6e38c4b..a8bbcaff 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -1,2 +1,3 @@ -pub mod feature; +pub mod connections; +pub mod features; pub mod invite; diff --git a/src/main.rs b/src/main.rs index 3b8a539e..6210b608 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,13 +1,21 @@ //! An Aries Cloudagent Controller to interact with Aries instances for data manipulation //! run `accf -e=XXX invite` to run the example script +#![warn( + clippy::all, + clippy::restriction, + clippy::nursery, + clippy::cargo +)] + #[macro_use] extern crate clap; -use crate::agent::agent::HttpAgentExtended; +use crate::agent::agents::HttpAgentExtended; use crate::agent::http_agent::HttpAgent; use crate::error::{throw, Error}; use clap::App; +use typing::{ConnectionsConfig, InvitationConfig}; mod agent; mod cli; @@ -22,7 +30,11 @@ async fn main() { let yaml = load_yaml!("../cli.yaml"); // Get all the supplied flags and values - let matches = App::from_yaml(yaml).get_matches(); + let matches = App::from_yaml(yaml) + .version(env!("CARGO_PKG_VERSION")) + .author(env!("CARGO_PKG_AUTHORS")) + .about(env!("CARGO_PKG_DESCRIPTION")) + .get_matches(); // create an httpAgent when you supply an endpoint let agent = match matches.value_of("endpoint") { @@ -33,19 +45,29 @@ async fn main() { agent.check_endpoint().await; // Matches the `feature` subcommand - if let Some(_) = matches.subcommand_matches("features") { - cli::feature::run(&agent).await + if matches.subcommand_matches("features").is_some() { + cli::features::run(&agent).await + } + + // Matches the `connections` subcommand + if let Some(matches_connections) = matches.subcommand_matches("connections") { + let id = matches_connections.value_of("id"); + let alias = matches_connections.value_of("alias"); + + let config = ConnectionsConfig { id, alias }; + + cli::connections::run(&agent, config).await } // Matches the `invite` subcommand - if let Some(matches_agent) = matches.subcommand_matches("invite") { - let auto_accept = matches_agent.is_present("auto-accept"); - let multi_use = matches_agent.is_present("multi-use"); - let alias = matches_agent.value_of("alias"); - let qr = matches_agent.is_present("qr"); - let toolbox = matches_agent.is_present("toolbox"); - - let config = typing::InviteConfiguration { + if let Some(matches_invite) = matches.subcommand_matches("invite") { + let auto_accept = matches_invite.is_present("auto-accept"); + let multi_use = matches_invite.is_present("multi-use"); + let alias = matches_invite.value_of("alias"); + let qr = matches_invite.is_present("qr"); + let toolbox = matches_invite.is_present("toolbox"); + + let config = InvitationConfig { auto_accept, multi_use, alias, diff --git a/src/typing.rs b/src/typing.rs index 881fdd36..14e7e96c 100644 --- a/src/typing.rs +++ b/src/typing.rs @@ -1,7 +1,7 @@ use serde::{Deserialize, Serialize}; use serde_json::{Map, Value}; -pub struct InviteConfiguration<'a> { +pub struct InvitationConfig<'a> { pub auto_accept: bool, pub multi_use: bool, pub alias: Option<&'a str>, @@ -9,6 +9,11 @@ pub struct InviteConfiguration<'a> { pub toolbox: bool, } +pub struct ConnectionsConfig<'a> { + pub alias: Option<&'a str>, + pub id: Option<&'a str>, +} + #[derive(Serialize, Deserialize)] pub struct InvitationOptions { pub alias: Option, diff --git a/src/utils/http.rs b/src/utils/http.rs index 9b251622..92c66431 100644 --- a/src/utils/http.rs +++ b/src/utils/http.rs @@ -1,12 +1,11 @@ -use std::collections::HashMap; - use reqwest::{Client, Url}; use serde::de::DeserializeOwned; +use serde_json::Value; use crate::error::{throw, Error}; // Handle calling of any endpoint with get -pub async fn get(url: Url, query: Option>) -> T { +pub async fn get(url: Url, query: Option>) -> T { let client = match query { Some(q) => Client::new().get(url).query(&q), None => Client::new().get(url), @@ -32,7 +31,7 @@ pub async fn get(url: Url, query: Option> pub async fn post( url: Url, query: Vec<(&str, String)>, - body: Option>>, + body: Option, ) -> T { let client = Client::new().post(url).query(&query); diff --git a/src/utils/qr.rs b/src/utils/qr.rs index faa1673c..bdbc580c 100644 --- a/src/utils/qr.rs +++ b/src/utils/qr.rs @@ -1,4 +1,4 @@ // Parse `Invitation to a `qr_code` -pub fn print_qr_code(text: &String) { +pub fn print_qr_code(text: &str) { qr2term::print_qr(text).expect("Error printing QR!"); } From f7925ed44ad52378251d9c1df9e8e7835018f159 Mon Sep 17 00:00:00 2001 From: Berend Sliedrecht Date: Sat, 11 Dec 2021 16:55:30 +0100 Subject: [PATCH 4/9] feat: added clippy and makefile Signed-off-by: Berend Sliedrecht --- Cargo.toml | 3 ++ Makefile | 11 ++++++ src/agent/agents.rs | 12 +++++++ src/agent/http_agent.rs | 36 ++++++++++++------- src/agent/mod.rs | 3 ++ src/cli/connections.rs | 1 + src/cli/features.rs | 1 + src/cli/invite.rs | 1 + src/cli/mod.rs | 5 +++ src/error.rs | 12 ++++--- src/main.rs | 17 +++++---- src/typing.rs | 77 ++++++++++++++++++++++++++++++----------- src/utils/http.rs | 4 +-- src/utils/logger.rs | 5 +-- src/utils/mod.rs | 5 +++ src/utils/qr.rs | 2 +- 16 files changed, 145 insertions(+), 50 deletions(-) create mode 100644 Makefile diff --git a/Cargo.toml b/Cargo.toml index e48791b3..3011dcc9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,9 @@ authors = ["Animo Solutions"] description = "A simple Aries Cloudagent Controller" homepage = "animo.id" edition = "2018" +repository = "https://github.com/animo/aries-datagenerator-toolkit" +keywords = ["aries", "cloudagent", "controller", "cli-tool"] +categories = ["aries", "cloudagent", "controller", "cli-tool"] [[bin]] name = "accf" diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..0a4a4a8d --- /dev/null +++ b/Makefile @@ -0,0 +1,11 @@ +clippy: + cargo clippy + +build: + cargo build + +run: + cargo run + +install: clippy build + cargo install --path . diff --git a/src/agent/agents.rs b/src/agent/agents.rs index f70c2681..a2c5757b 100644 --- a/src/agent/agents.rs +++ b/src/agent/agents.rs @@ -1,16 +1,28 @@ use crate::typing::{Connection, Connections, Feature, Invitation, InvitationConfig}; use async_trait::async_trait; +/// base cloudagent functionality #[async_trait] pub trait Agent { + /// Gets all the connections async fn get_connections(&self, filter: Option<&str>) -> Connections; + + /// Get a connection by id async fn get_connection_by_id(&self, id: &str) -> Connection; + + /// Prints an invitation, as url or qr, in stdout async fn create_invitation(&self, config: &InvitationConfig<'_>) -> Invitation; + + /// Requests all the features from the cloudagent async fn discover_features(&self) -> Feature; } +/// HTTP specific cloudagent functionality #[async_trait] pub trait HttpAgentExtended { + /// New http agent instance fn new(endpoint: &str) -> Self; + + /// Check if the endpoint is valid async fn check_endpoint(&self) -> (); } diff --git a/src/agent/http_agent.rs b/src/agent/http_agent.rs index c7e5a162..ee1cdee1 100644 --- a/src/agent/http_agent.rs +++ b/src/agent/http_agent.rs @@ -5,44 +5,49 @@ use async_trait::async_trait; use reqwest::Url; use serde_json::json; +/// HTTP cloudagent #[derive(Debug, Clone)] pub struct HttpAgent { + /// base url of the cloudagent url: String, } -// All the available endpoints +/// All the available endpoints struct Endpoint; -// Default value for every endpoint -// TODO: Not the most efficient mehtod (creates a new instance for every function call) +/// Default value for every endpoint impl Endpoint { + /// base + connections fn connections(url: &str) -> Url { reqwest::Url::parse(url) - .expect(&format!("Could not join on {}", url)) + .unwrap_or_else(|_| panic!("Could not parse {}", url)) .join("connections") - .expect(&format!("Could not join on connections")) + .unwrap_or_else(|_| panic!("Could not join on connections")) } + /// base + connections + :id fn get_connection_by_id(url: &str, id: &str) -> Url { reqwest::Url::parse(url) - .expect(&format!("Could not join on {}", url)) + .unwrap_or_else(|_| panic!("Could not parse {}", url)) .join("connections/") - .expect(&format!("Could not join on connections/")) + .unwrap_or_else(|_| panic!("Could not join on connections")) .join(id) - .expect(&format!("Could not join on {}", id)) + .unwrap_or_else(|_| panic!("Could not join on {}", id)) } + /// base + connections + create-invitation fn create_invitation(url: &str) -> Url { reqwest::Url::parse(url) - .expect(&format!("Could not join on {}", url)) + .unwrap_or_else(|_| panic!("Could not parse {}", url)) .join("connections/") - .expect(&format!("Could not join on connections/")) + .unwrap_or_else(|_| panic!("Could not join on connections")) .join("create-invitation") - .expect(&format!("Could not join on create-invitation")) + .unwrap_or_else(|_| panic!("Could not join on create-invitation")) } + /// base + features fn discover_features(url: &str) -> Url { reqwest::Url::parse(url) - .expect(&format!("Could not join on {}", url)) + .unwrap_or_else(|_| panic!("Could not parse {}", url)) .join("features") - .expect(&format!("Could not join on features")) + .unwrap_or_else(|_| panic!("Could not join on features")) } } @@ -54,6 +59,7 @@ impl HttpAgentExtended for HttpAgent { } } + /// Check if the endpoint is valid async fn check_endpoint(&self) -> () { http::get::(Endpoint::connections(&self.url), None).await; } @@ -61,6 +67,7 @@ impl HttpAgentExtended for HttpAgent { #[async_trait] impl Agent for HttpAgent { + /// Gets all the connections async fn get_connections(&self, filter: Option<&str>) -> Connections { let mut query: Vec<(&str, String)> = vec![]; @@ -71,10 +78,12 @@ impl Agent for HttpAgent { http::get::(Endpoint::connections(&self.url), Some(query)).await } + /// Get a connection by id async fn get_connection_by_id(&self, id: &str) -> typing::Connection { http::get::(Endpoint::get_connection_by_id(&self.url, id), None).await } + /// Prints an invitation, as url or qr, in stdout async fn create_invitation(&self, config: &typing::InvitationConfig<'_>) -> typing::Invitation { let mut query: Vec<(&str, String)> = vec![]; let mut body = None; @@ -104,6 +113,7 @@ impl Agent for HttpAgent { http::post(Endpoint::create_invitation(&self.url), query, body).await } + /// Requests all the features from the cloudagent async fn discover_features(&self) -> Feature { http::get::(Endpoint::discover_features(&self.url), None).await } diff --git a/src/agent/mod.rs b/src/agent/mod.rs index d6fc78b5..edf034df 100644 --- a/src/agent/mod.rs +++ b/src/agent/mod.rs @@ -1,2 +1,5 @@ +/// base agent pub mod agents; + +/// HTTP agent pub mod http_agent; diff --git a/src/cli/connections.rs b/src/cli/connections.rs index b7233e85..fcfcbb3d 100644 --- a/src/cli/connections.rs +++ b/src/cli/connections.rs @@ -3,6 +3,7 @@ use crate::typing::ConnectionsConfig; use crate::utils::logger::Log; use serde::Serialize; +/// CLI runner for the `connections` subcommand pub async fn run(agent: &dyn Agent, config: ConnectionsConfig<'_>) { match config.id { Some(i) => { diff --git a/src/cli/features.rs b/src/cli/features.rs index 18e8e6c6..e7da8b57 100644 --- a/src/cli/features.rs +++ b/src/cli/features.rs @@ -1,6 +1,7 @@ use crate::agent::agents::Agent; use crate::utils::logger::Log; +/// CLI runner for the `features` subcommand pub async fn run(agent: &dyn Agent) { let features = agent.discover_features().await; for (_, item) in features.results.iter().enumerate() { diff --git a/src/cli/invite.rs b/src/cli/invite.rs index 0dd29a1d..ecfa2850 100644 --- a/src/cli/invite.rs +++ b/src/cli/invite.rs @@ -3,6 +3,7 @@ use crate::typing::InvitationConfig; use crate::utils::logger::Log; use crate::utils::qr; +/// CLI runner for the `invite` subcommand pub async fn run(agent: &dyn Agent, config: InvitationConfig<'_>) { let invitation = agent.create_invitation(&config).await; diff --git a/src/cli/mod.rs b/src/cli/mod.rs index a8bbcaff..56633732 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -1,3 +1,8 @@ +/// connections subcommand pub mod connections; + +/// features subcommand pub mod features; + +/// invite subcommand pub mod invite; diff --git a/src/error.rs b/src/error.rs index fe95eddc..7643517d 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,20 +1,22 @@ use crate::utils::logger::Log; -// Error types +/// Error types pub enum Error { + /// The provided endpoint is not a valid one for a cloudagent InvalidEndpoint, + + /// Response from the server could not be parsed ServerResponseParseError, + + /// Something went wrong on the server-side InternalServerError, } -// Error handler (Should not panic but print a custom error and exit) +/// Error handler (Should not panic but print a custom error and exit) pub fn throw(error: Error) -> ! { match error { - // The endpoint in the configuration file is invalid Error::InvalidEndpoint => Log::error("Invalid Endpoint"), - // Could not parse the response from the server Error::ServerResponseParseError => Log::error("Unable to parse response from server"), - // The server did not respond with a success code Error::InternalServerError => Log::error("Internal Server Error"), } } diff --git a/src/main.rs b/src/main.rs index 6210b608..679725e7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,12 +1,8 @@ //! An Aries Cloudagent Controller to interact with Aries instances for data manipulation //! run `accf -e=XXX invite` to run the example script -#![warn( - clippy::all, - clippy::restriction, - clippy::nursery, - clippy::cargo -)] +#![allow(clippy::enum_variant_names)] +#![warn(missing_docs, clippy::missing_docs_in_private_items)] #[macro_use] extern crate clap; @@ -17,10 +13,19 @@ use crate::error::{throw, Error}; use clap::App; use typing::{ConnectionsConfig, InvitationConfig}; +/// agent mod agent; + +/// cli mod cli; + +/// error mod error; + +/// typing mod typing; + +/// utils mod utils; /// Initializes the application diff --git a/src/typing.rs b/src/typing.rs index 14e7e96c..237149d1 100644 --- a/src/typing.rs +++ b/src/typing.rs @@ -1,70 +1,105 @@ use serde::{Deserialize, Serialize}; use serde_json::{Map, Value}; +/// Type of the invitation configuration as received by the cli pub struct InvitationConfig<'a> { + /// Whether the invitation should auto accept pub auto_accept: bool, + + /// Whether the invitation should be multi use pub multi_use: bool, + + /// Alias for the connection that will be created with that invitation pub alias: Option<&'a str>, + + /// Whether it will print a qr code instead of a url pub qr: bool, + + /// Whether it should use a pre-configured toolbox configuration pub toolbox: bool, } +/// Type of the connections configuration as received by the cli pub struct ConnectionsConfig<'a> { + /// Filter connections by this alias pub alias: Option<&'a str>, - pub id: Option<&'a str>, -} -#[derive(Serialize, Deserialize)] -pub struct InvitationOptions { - pub alias: Option, - pub auto_accept: Option, - pub multi_use: Option, + /// Get a connection by this id + pub id: Option<&'a str>, } +/// Type of the received connections list #[derive(Debug, Serialize, Deserialize, Clone)] pub struct Connections { + /// List of the current connections pub results: Vec, } +/// Type of a single received connection #[derive(Debug, Serialize, Deserialize, Clone)] pub struct Connection { + /// Invitation mode pub invitation_mode: String, + + /// Aries rfc23 state pub rfc23_state: String, + + /// Key of the invitation pub invitation_key: String, + + /// Their label pub their_label: Option, + + /// Auto acceptance pub accept: String, + + /// Their did pub their_did: Option, + + /// Timestamp of when the connection was created pub created_at: String, + + /// Their role in the invitation process pub their_role: String, + + /// When the connection was updated pub updated_at: String, + + /// State of the routing pub routing_state: String, + + /// The id of the connection pub connection_id: String, + + /// Your own did used for this connection pub my_did: Option, + + /// State of the connection pub state: String, + + /// Alias given for this connection pub alias: Option, } -#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] +/// Type of the received invitation +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct Invitation { + /// Connection id pub connection_id: String, - pub invitation: MetaInvitation, + + /// Invitation object + pub invitation: Map, + + /// Invitation url that can be used to accept it by another party pub invitation_url: String, - pub alias: Option, -} -#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct MetaInvitation { - #[serde(rename = "@type")] - pub type_field: String, - #[serde(rename = "@id")] - pub id: String, - pub service_endpoint: String, - pub recipient_keys: Vec, - pub label: String, + /// Alias for the given invitation + pub alias: Option, } +/// Type of the received features from `discover-features` #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct Feature { + /// List of all the features the cloudagent supports pub results: Map, } diff --git a/src/utils/http.rs b/src/utils/http.rs index 92c66431..3f36a958 100644 --- a/src/utils/http.rs +++ b/src/utils/http.rs @@ -4,7 +4,7 @@ use serde_json::Value; use crate::error::{throw, Error}; -// Handle calling of any endpoint with get +/// Handle calling of any endpoint with get pub async fn get(url: Url, query: Option>) -> T { let client = match query { Some(q) => Client::new().get(url).query(&q), @@ -27,7 +27,7 @@ pub async fn get(url: Url, query: Option( url: Url, query: Vec<(&str, String)>, diff --git a/src/utils/logger.rs b/src/utils/logger.rs index 3564afe6..1cda9e43 100644 --- a/src/utils/logger.rs +++ b/src/utils/logger.rs @@ -1,14 +1,15 @@ use colored::*; +/// Logger struct that allows us access to stdout and stderr pub struct Log; impl Log { - // Log messages that contain generic info + /// Log messages that contain generic info pub fn log(string: &str) { println!("{}", String::from(string)); } - // Log messages that broke the program + /// Log messages that broke the program pub fn error(string: &str) -> ! { eprintln!("{}: {}", "Error".red(), String::from(string)); std::process::exit(1) diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 3f35a8b0..e965580f 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,3 +1,8 @@ +/// Module that allows the `http_agent` to make `get` and `post` requests pub mod http; + +/// Module that allows any `agent` to give output to stdout and stderr pub mod logger; + +/// Module that allows to print a qr code to stdout pub mod qr; diff --git a/src/utils/qr.rs b/src/utils/qr.rs index bdbc580c..191cc938 100644 --- a/src/utils/qr.rs +++ b/src/utils/qr.rs @@ -1,4 +1,4 @@ -// Parse `Invitation to a `qr_code` +/// Parse `Invitation to a `qr_code` pub fn print_qr_code(text: &str) { qr2term::print_qr(text).expect("Error printing QR!"); } From cb68ab3d7d44d5dfdbf05b6ebe077b20cd475bb4 Mon Sep 17 00:00:00 2001 From: Berend Sliedrecht Date: Sat, 11 Dec 2021 17:00:00 +0100 Subject: [PATCH 5/9] feat: added cargo doc to makefile Signed-off-by: Berend Sliedrecht --- Makefile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Makefile b/Makefile index 0a4a4a8d..9d6d4df8 100644 --- a/Makefile +++ b/Makefile @@ -7,5 +7,8 @@ build: run: cargo run +docs: + cargo doc --open + install: clippy build cargo install --path . From 67e83ccf7b36410de3916a4eb5a6089d317c5324 Mon Sep 17 00:00:00 2001 From: Berend Sliedrecht Date: Sun, 12 Dec 2021 15:03:29 +0100 Subject: [PATCH 6/9] feat: basic messages Signed-off-by: Berend Sliedrecht --- cli.yaml | 16 ++++++++++++++++ src/agent/agents.rs | 9 +++++++-- src/agent/http_agent.rs | 39 +++++++++++++++++++++++++++++++++------ src/cli/message.rs | 16 ++++++++++++++++ src/cli/mod.rs | 9 ++++++--- src/main.rs | 13 ++++++++++++- src/typing.rs | 11 ++++++++++- src/utils/http.rs | 10 ++++++---- 8 files changed, 106 insertions(+), 17 deletions(-) create mode 100644 src/cli/message.rs diff --git a/cli.yaml b/cli.yaml index c26cd001..ebd2b54c 100644 --- a/cli.yaml +++ b/cli.yaml @@ -57,3 +57,19 @@ subcommands: - features: about: discover features subcommand version: "0.1.0" + - message: + about: basic message subcommand + version: "0.1.0" + args: + - id: + help: connection id + short: i + long: id + takes_value: true + required: true + - message: + help: message to send to the connection + short: m + long: message + takes_value: true + required: true diff --git a/src/agent/agents.rs b/src/agent/agents.rs index a2c5757b..b12bb8bf 100644 --- a/src/agent/agents.rs +++ b/src/agent/agents.rs @@ -1,4 +1,6 @@ -use crate::typing::{Connection, Connections, Feature, Invitation, InvitationConfig}; +use crate::typing::{ + Connection, Connections, Features, Invitation, InvitationConfig, MessageConfig, +}; use async_trait::async_trait; /// base cloudagent functionality @@ -14,7 +16,10 @@ pub trait Agent { async fn create_invitation(&self, config: &InvitationConfig<'_>) -> Invitation; /// Requests all the features from the cloudagent - async fn discover_features(&self) -> Feature; + async fn discover_features(&self) -> Features; + + /// Send a basic message to another agent + async fn send_message(&self, config: &MessageConfig<'_>) -> (); } /// HTTP specific cloudagent functionality diff --git a/src/agent/http_agent.rs b/src/agent/http_agent.rs index ee1cdee1..796b94f1 100644 --- a/src/agent/http_agent.rs +++ b/src/agent/http_agent.rs @@ -1,5 +1,7 @@ use crate::agent::agents::{Agent, HttpAgentExtended}; -use crate::typing::{self, Connection, Connections, Feature}; +use crate::typing::{ + Connection, Connections, Features, Invitation, InvitationConfig, MessageConfig, +}; use crate::utils::http; use async_trait::async_trait; use reqwest::Url; @@ -49,6 +51,17 @@ impl Endpoint { .join("features") .unwrap_or_else(|_| panic!("Could not join on features")) } + /// base + connections + :id + send-message + fn basic_message(url: &str, id: &str) -> Url { + reqwest::Url::parse(url) + .unwrap_or_else(|_| panic!("Could not parse {}", url)) + .join("connections/") + .unwrap_or_else(|_| panic!("Could not join on connections")) + .join(&format!("{}/", id)) + .unwrap_or_else(|_| panic!("Could not join on {}", id)) + .join("send-message") + .unwrap_or_else(|_| panic!("Could not join on send-message")) + } } #[async_trait] @@ -79,12 +92,12 @@ impl Agent for HttpAgent { } /// Get a connection by id - async fn get_connection_by_id(&self, id: &str) -> typing::Connection { + async fn get_connection_by_id(&self, id: &str) -> Connection { http::get::(Endpoint::get_connection_by_id(&self.url, id), None).await } /// Prints an invitation, as url or qr, in stdout - async fn create_invitation(&self, config: &typing::InvitationConfig<'_>) -> typing::Invitation { + async fn create_invitation(&self, config: &InvitationConfig<'_>) -> Invitation { let mut query: Vec<(&str, String)> = vec![]; let mut body = None; @@ -110,11 +123,25 @@ impl Agent for HttpAgent { } } - http::post(Endpoint::create_invitation(&self.url), query, body).await + http::post(Endpoint::create_invitation(&self.url), Some(query), body).await } /// Requests all the features from the cloudagent - async fn discover_features(&self) -> Feature { - http::get::(Endpoint::discover_features(&self.url), None).await + async fn discover_features(&self) -> Features { + http::get::(Endpoint::discover_features(&self.url), None).await + } + + /// Send a basic message to another agent + async fn send_message(&self, config: &MessageConfig<'_>) -> () { + let body = json!({ + "content": config.message, + }); + + http::post::( + Endpoint::basic_message(&self.url, config.id), + None, + Some(body), + ) + .await; } } diff --git a/src/cli/message.rs b/src/cli/message.rs new file mode 100644 index 00000000..b5f84b3b --- /dev/null +++ b/src/cli/message.rs @@ -0,0 +1,16 @@ +use colored::Colorize; + +use crate::agent::agents::Agent; +use crate::typing::MessageConfig; +use crate::utils::logger::Log; + +/// CLI runner for the `message` subcommand +pub async fn run(agent: &dyn Agent, config: MessageConfig<'_>) { + agent.send_message(&config).await; + + Log::log(&format!( + "Sent \"{}\" to {}!", + config.message.bright_purple(), + config.id.cyan() + )); +} diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 56633732..1c455aea 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -1,8 +1,11 @@ -/// connections subcommand +/// Connections subcommand pub mod connections; -/// features subcommand +/// Features subcommand pub mod features; -/// invite subcommand +/// Invite subcommand pub mod invite; + +/// Basic message subcommand +pub mod message; diff --git a/src/main.rs b/src/main.rs index 679725e7..b223625e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,7 +11,7 @@ use crate::agent::agents::HttpAgentExtended; use crate::agent::http_agent::HttpAgent; use crate::error::{throw, Error}; use clap::App; -use typing::{ConnectionsConfig, InvitationConfig}; +use typing::{ConnectionsConfig, InvitationConfig, MessageConfig}; /// agent mod agent; @@ -54,6 +54,17 @@ async fn main() { cli::features::run(&agent).await } + // Matches the `message` subcommand + if let Some(matches_connections) = matches.subcommand_matches("message") { + // We can use unwrap here because these values are required by the cli + let id = matches_connections.value_of("id").unwrap(); + let message = matches_connections.value_of("message").unwrap(); + + let config = MessageConfig { id, message }; + + cli::message::run(&agent, config).await + } + // Matches the `connections` subcommand if let Some(matches_connections) = matches.subcommand_matches("connections") { let id = matches_connections.value_of("id"); diff --git a/src/typing.rs b/src/typing.rs index 237149d1..0a977639 100644 --- a/src/typing.rs +++ b/src/typing.rs @@ -28,6 +28,15 @@ pub struct ConnectionsConfig<'a> { pub id: Option<&'a str>, } +/// Type of the message configuration as received by the cli +pub struct MessageConfig<'a> { + /// id to send the message to + pub id: &'a str, + + /// The message to send + pub message: &'a str, +} + /// Type of the received connections list #[derive(Debug, Serialize, Deserialize, Clone)] pub struct Connections { @@ -99,7 +108,7 @@ pub struct Invitation { /// Type of the received features from `discover-features` #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct Feature { +pub struct Features { /// List of all the features the cloudagent supports pub results: Map, } diff --git a/src/utils/http.rs b/src/utils/http.rs index 3f36a958..2e4e9946 100644 --- a/src/utils/http.rs +++ b/src/utils/http.rs @@ -30,16 +30,18 @@ pub async fn get(url: Url, query: Option( url: Url, - query: Vec<(&str, String)>, + query: Option>, body: Option, ) -> T { let client = Client::new().post(url).query(&query); - let response = match body { - Some(b) => client.json(&b).send().await, - None => client.send().await, + let client = match body { + Some(b) => client.json(&b), + None => client, }; + let response = client.send().await; + match response { Ok(res) => { if res.status().is_success() { From c18bf35e223c49a912e8f5c19c4e334b460d7e39 Mon Sep 17 00:00:00 2001 From: Berend Sliedrecht Date: Mon, 13 Dec 2021 10:24:07 +0100 Subject: [PATCH 7/9] feat: issue credential and module refactor Signed-off-by: Berend Sliedrecht --- cli.yaml | 56 ++++++++++++++++++++++------- src/agent/agents.rs | 16 +++++---- src/agent/http_agent.rs | 51 +++++++++++++++++++------- src/cli/connections.rs | 55 +++++++++++++++++----------- src/cli/features.rs | 25 ++++++++++--- src/cli/invite.rs | 45 +++++++++++++++++++---- src/cli/issue_credential.rs | 51 ++++++++++++++++++++++++++ src/cli/message.rs | 46 ++++++++++++++++++------ src/cli/mod.rs | 6 ++++ src/cli/register.rs | 48 +++++++++++++++++++++++++ src/main.rs | 71 ++----------------------------------- src/typing.rs | 31 +++++++++++----- src/utils/logger.rs | 12 +++++++ 13 files changed, 362 insertions(+), 151 deletions(-) create mode 100644 src/cli/issue_credential.rs create mode 100644 src/cli/register.rs diff --git a/cli.yaml b/cli.yaml index ebd2b54c..bfc655f9 100644 --- a/cli.yaml +++ b/cli.yaml @@ -47,7 +47,7 @@ subcommands: - id: help: connection id short: i - long: id + long: connection-id takes_value: true - alias: help: filter on alias @@ -61,15 +61,45 @@ subcommands: about: basic message subcommand version: "0.1.0" args: - - id: - help: connection id - short: i - long: id - takes_value: true - required: true - - message: - help: message to send to the connection - short: m - long: message - takes_value: true - required: true + - connection-id: + help: connection id + short: i + long: connection-id + takes_value: true + required: true + - message: + help: message to send to the connection + short: m + long: message + takes_value: true + required: true + - issue-credential: + about: issue credentials subcommand + version: "0.1.0" + args: + - connection-id: + help: connection id + short: i + long: connection-id + required: true + takes_value: true + - credential-definition-id: + help: credential definition id + short: d + long: cred-def-id + required: true + takes_value: true + - key: + help: key + short: k + long: key + required: true + takes_value: true + multiple: true + - value: + help: value + short: v + long: value + required: true + takes_value: true + multiple: true diff --git a/src/agent/agents.rs b/src/agent/agents.rs index b12bb8bf..d57cb6ad 100644 --- a/src/agent/agents.rs +++ b/src/agent/agents.rs @@ -1,5 +1,6 @@ use crate::typing::{ - Connection, Connections, Features, Invitation, InvitationConfig, MessageConfig, + Connection, Connections, Features, Invitation, InvitationConfig, IssueCredentialConfig, + MessageConfig, }; use async_trait::async_trait; @@ -7,26 +8,29 @@ use async_trait::async_trait; #[async_trait] pub trait Agent { /// Gets all the connections - async fn get_connections(&self, filter: Option<&str>) -> Connections; + async fn get_connections(&self, filter: Option) -> Connections; /// Get a connection by id - async fn get_connection_by_id(&self, id: &str) -> Connection; + async fn get_connection_by_id(&self, id: String) -> Connection; /// Prints an invitation, as url or qr, in stdout - async fn create_invitation(&self, config: &InvitationConfig<'_>) -> Invitation; + async fn create_invitation(&self, config: &InvitationConfig) -> Invitation; /// Requests all the features from the cloudagent async fn discover_features(&self) -> Features; /// Send a basic message to another agent - async fn send_message(&self, config: &MessageConfig<'_>) -> (); + async fn send_message(&self, config: &MessageConfig) -> (); + + /// Offer a credential to another agent + async fn offer_credential(&self, config: &IssueCredentialConfig) -> (); } /// HTTP specific cloudagent functionality #[async_trait] pub trait HttpAgentExtended { /// New http agent instance - fn new(endpoint: &str) -> Self; + fn new(endpoint: String) -> Self; /// Check if the endpoint is valid async fn check_endpoint(&self) -> (); diff --git a/src/agent/http_agent.rs b/src/agent/http_agent.rs index 796b94f1..0703d9e7 100644 --- a/src/agent/http_agent.rs +++ b/src/agent/http_agent.rs @@ -1,8 +1,10 @@ use crate::agent::agents::{Agent, HttpAgentExtended}; use crate::typing::{ - Connection, Connections, Features, Invitation, InvitationConfig, MessageConfig, + Connection, Connections, Features, Invitation, InvitationConfig, IssueCredentialConfig, + MessageConfig, }; use crate::utils::http; +use crate::utils::logger::Log; use async_trait::async_trait; use reqwest::Url; use serde_json::json; @@ -62,14 +64,21 @@ impl Endpoint { .join("send-message") .unwrap_or_else(|_| panic!("Could not join on send-message")) } + /// base + issue-credential + send-offer + fn credential_offer(url: &str) -> Url { + reqwest::Url::parse(url) + .unwrap_or_else(|_| panic!("Could not parse {}", url)) + .join("issue-credential") + .unwrap_or_else(|_| panic!("Could not join on issue-credential")) + .join("send-offer") + .unwrap_or_else(|_| panic!("Could not join on send-offer")) + } } #[async_trait] impl HttpAgentExtended for HttpAgent { - fn new(endpoint: &str) -> Self { - HttpAgent { - url: endpoint.to_owned(), - } + fn new(endpoint: String) -> Self { + HttpAgent { url: endpoint } } /// Check if the endpoint is valid @@ -81,23 +90,23 @@ impl HttpAgentExtended for HttpAgent { #[async_trait] impl Agent for HttpAgent { /// Gets all the connections - async fn get_connections(&self, filter: Option<&str>) -> Connections { + async fn get_connections(&self, filter: Option) -> Connections { let mut query: Vec<(&str, String)> = vec![]; if let Some(alias) = filter { - query.push(("alias", alias.to_string())); + query.push(("alias", alias)); } http::get::(Endpoint::connections(&self.url), Some(query)).await } /// Get a connection by id - async fn get_connection_by_id(&self, id: &str) -> Connection { - http::get::(Endpoint::get_connection_by_id(&self.url, id), None).await + async fn get_connection_by_id(&self, id: String) -> Connection { + http::get::(Endpoint::get_connection_by_id(&self.url, &id), None).await } /// Prints an invitation, as url or qr, in stdout - async fn create_invitation(&self, config: &InvitationConfig<'_>) -> Invitation { + async fn create_invitation(&self, config: &InvitationConfig) -> Invitation { let mut query: Vec<(&str, String)> = vec![]; let mut body = None; @@ -118,7 +127,7 @@ impl Agent for HttpAgent { query.push(multi_use); query.push(auto_accept); - if let Some(alias) = config.alias { + if let Some(alias) = &config.alias { query.push(("alias", alias.to_string())); } } @@ -132,16 +141,32 @@ impl Agent for HttpAgent { } /// Send a basic message to another agent - async fn send_message(&self, config: &MessageConfig<'_>) -> () { + async fn send_message(&self, config: &MessageConfig) -> () { let body = json!({ "content": config.message, }); http::post::( - Endpoint::basic_message(&self.url, config.id), + Endpoint::basic_message(&self.url, &config.connection_id), None, Some(body), ) .await; } + + async fn offer_credential(&self, config: &IssueCredentialConfig) -> () { + let body = json!({ + "connection_id": config.connection_id, + "cred_def_id": config.credential_definition_id, + "credential_preview": { + "@type": "issue-credential/1.0/credential-preview", + "attributes": config.attributes + } + }); + + Log::log_pretty(config); + + http::post::(Endpoint::credential_offer(&self.url), None, Some(body)) + .await; + } } diff --git a/src/cli/connections.rs b/src/cli/connections.rs index fcfcbb3d..871c9f84 100644 --- a/src/cli/connections.rs +++ b/src/cli/connections.rs @@ -1,32 +1,47 @@ +use super::register::Module; use crate::agent::agents::Agent; use crate::typing::ConnectionsConfig; use crate::utils::logger::Log; -use serde::Serialize; +use async_trait::async_trait; +use clap::ArgMatches; -/// CLI runner for the `connections` subcommand -pub async fn run(agent: &dyn Agent, config: ConnectionsConfig<'_>) { - match config.id { - Some(i) => { - let connection = agent.get_connection_by_id(i).await; +/// Connections module for the agent +pub struct ConnectionsModule; - let buf = Vec::new(); - let formatter = serde_json::ser::PrettyFormatter::with_indent(b" "); - let mut ser = serde_json::Serializer::with_formatter(buf, formatter); +/// Implementation of a module for connections +#[async_trait(?Send)] +impl Module for ConnectionsModule { + async fn run(agent: &dyn Agent, config: ConnectionsConfig) { + match config.connection_id { + Some(id) => { + let connection = agent.get_connection_by_id(id).await; - connection.serialize(&mut ser).unwrap(); + Log::log_pretty(connection); + } + None => { + let connections = agent.get_connections(config.alias).await.results; - Log::log(&String::from_utf8(ser.into_inner()).unwrap()); - } - None => { - let connections = agent.get_connections(config.alias).await.results; + Log::log_pretty(connections); + } + }; + } + + async fn register<'a>(agent: &dyn Agent, matches: &ArgMatches<'a>) { + if let Some(matches_connections) = matches.subcommand_matches("connections") { + let connection_id = matches_connections + .value_of("connection-id") + .map(|id| id.to_string()); - let buf = Vec::new(); - let formatter = serde_json::ser::PrettyFormatter::with_indent(b" "); - let mut ser = serde_json::Serializer::with_formatter(buf, formatter); + let alias = matches_connections + .value_of("alias") + .map(|alias| alias.to_string()); - connections.serialize(&mut ser).unwrap(); + let config = ConnectionsConfig { + connection_id, + alias, + }; - Log::log(&String::from_utf8(ser.into_inner()).unwrap()); + ConnectionsModule::run(agent, config).await; } - }; + } } diff --git a/src/cli/features.rs b/src/cli/features.rs index e7da8b57..79c32a6c 100644 --- a/src/cli/features.rs +++ b/src/cli/features.rs @@ -1,10 +1,25 @@ +use super::register::Module; use crate::agent::agents::Agent; use crate::utils::logger::Log; +use async_trait::async_trait; +use clap::ArgMatches; -/// CLI runner for the `features` subcommand -pub async fn run(agent: &dyn Agent) { - let features = agent.discover_features().await; - for (_, item) in features.results.iter().enumerate() { - Log::log(item.0); +/// Features module for the agent +pub struct FeaturesModule; + +/// Implementation of a module for features +#[async_trait(?Send)] +impl Module<()> for FeaturesModule { + async fn run(agent: &dyn Agent, _: ()) { + let features = agent.discover_features().await; + for (_, item) in features.results.iter().enumerate() { + Log::log(item.0); + } + } + + async fn register<'a>(agent: &dyn Agent, matches: &ArgMatches<'a>) { + if matches.subcommand_matches("features").is_some() { + FeaturesModule::run(agent, ()).await; + } } } diff --git a/src/cli/invite.rs b/src/cli/invite.rs index ecfa2850..94899680 100644 --- a/src/cli/invite.rs +++ b/src/cli/invite.rs @@ -1,15 +1,46 @@ +use super::register::Module; use crate::agent::agents::Agent; use crate::typing::InvitationConfig; use crate::utils::logger::Log; use crate::utils::qr; +use async_trait::async_trait; +use clap::ArgMatches; -/// CLI runner for the `invite` subcommand -pub async fn run(agent: &dyn Agent, config: InvitationConfig<'_>) { - let invitation = agent.create_invitation(&config).await; +/// Invitations module for the agent +pub struct InvitationsModule; - if config.qr { - qr::print_qr_code(&invitation.invitation_url); - } else { - Log::log(&invitation.invitation_url); +/// Implementation of a module for invitations +#[async_trait(?Send)] +impl Module for InvitationsModule { + async fn run(agent: &dyn Agent, config: InvitationConfig) { + let invitation = agent.create_invitation(&config).await; + + if config.qr { + qr::print_qr_code(&invitation.invitation_url); + } else { + Log::log(&invitation.invitation_url); + } + } + + async fn register<'a>(agent: &dyn Agent, matches: &ArgMatches<'a>) { + if let Some(matches_invite) = matches.subcommand_matches("invite") { + let auto_accept = matches_invite.is_present("auto-accept"); + let multi_use = matches_invite.is_present("multi-use"); + let qr = matches_invite.is_present("qr"); + let toolbox = matches_invite.is_present("toolbox"); + let alias = matches_invite + .value_of("alias") + .map(|alias| alias.to_string()); + + let config = InvitationConfig { + auto_accept, + multi_use, + alias, + qr, + toolbox, + }; + + InvitationsModule::run(agent, config).await; + } } } diff --git a/src/cli/issue_credential.rs b/src/cli/issue_credential.rs new file mode 100644 index 00000000..5ae52dbb --- /dev/null +++ b/src/cli/issue_credential.rs @@ -0,0 +1,51 @@ +use std::iter::zip; + +use super::register::Module; +use crate::agent::agents::Agent; +use crate::typing::IssueCredentialConfig; +use crate::utils::logger::Log; +use async_trait::async_trait; +use clap::ArgMatches; +use serde_json::json; + +/// Credentials module for the agent +pub struct CredentialsModule; + +/// Implementation of a module for credentials +#[async_trait(?Send)] +impl Module for CredentialsModule { + async fn run(agent: &dyn Agent, config: IssueCredentialConfig) { + let credential = agent.offer_credential(&config).await; + + Log::log_pretty(credential); + } + + async fn register<'a>(agent: &dyn Agent, matches: &ArgMatches<'a>) { + if let Some(matches_connections) = matches.subcommand_matches("issue-credential") { + // We can use unwrap here because these values are required by the cli + let keys: Vec<&str> = matches_connections.values_of("key").unwrap().collect(); + let values: Vec<&str> = matches_connections.values_of("value").unwrap().collect(); + let connection_id = matches_connections + .value_of("connection-id") + .unwrap() + .to_string(); + let credential_definition_id = matches_connections + .value_of("credential-definition-id") + .unwrap() + .to_string(); + let mut attributes = vec![]; + + for (key, value) in zip(keys, values) { + attributes.push(json!({"name": key, "value": value})); + } + + let config = IssueCredentialConfig { + connection_id, + credential_definition_id, + attributes, + }; + + CredentialsModule::run(agent, config).await; + } + } +} diff --git a/src/cli/message.rs b/src/cli/message.rs index b5f84b3b..463c055b 100644 --- a/src/cli/message.rs +++ b/src/cli/message.rs @@ -1,16 +1,42 @@ -use colored::Colorize; - +use super::register::Module; use crate::agent::agents::Agent; use crate::typing::MessageConfig; use crate::utils::logger::Log; +use async_trait::async_trait; +use clap::ArgMatches; +use colored::Colorize; + +/// Messages module for the agent +pub struct MessagesModule; + +/// Implementation of a module for messages +#[async_trait(?Send)] +impl Module for MessagesModule { + async fn run(agent: &dyn Agent, config: MessageConfig) { + agent.send_message(&config).await; + + Log::log(&format!( + "Sent \"{}\" to {}!", + config.message.bright_purple(), + config.connection_id.cyan() + )); + } + + async fn register<'a>(agent: &dyn Agent, matches: &ArgMatches<'a>) { + if let Some(matches_connections) = matches.subcommand_matches("message") { + // We can use unwrap here because these values are required by the cli + let connection_id = matches_connections + .value_of("connection-id") + .unwrap() + .to_string(); + let message = matches_connections.value_of("message").unwrap().to_string(); -/// CLI runner for the `message` subcommand -pub async fn run(agent: &dyn Agent, config: MessageConfig<'_>) { - agent.send_message(&config).await; + let config = MessageConfig { + connection_id, + message, + }; - Log::log(&format!( - "Sent \"{}\" to {}!", - config.message.bright_purple(), - config.id.cyan() - )); + MessagesModule::run(agent, config).await; + } + } } diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 1c455aea..e54f45dc 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -1,3 +1,6 @@ +/// Register the cli component +pub mod register; + /// Connections subcommand pub mod connections; @@ -9,3 +12,6 @@ pub mod invite; /// Basic message subcommand pub mod message; + +/// Issue credential V1 subcommand +pub mod issue_credential; diff --git a/src/cli/register.rs b/src/cli/register.rs new file mode 100644 index 00000000..81938601 --- /dev/null +++ b/src/cli/register.rs @@ -0,0 +1,48 @@ +use super::connections::ConnectionsModule; +use super::features::FeaturesModule; +use super::invite::InvitationsModule; +use super::issue_credential::CredentialsModule; +use super::message::MessagesModule; +use crate::agent::agents::{Agent, HttpAgentExtended}; +use crate::agent::http_agent::HttpAgent; +use crate::error::{throw, Error}; +use async_trait::async_trait; +use clap::{App, ArgMatches}; + +/// trait that every submodule MUST implement +#[async_trait(?Send)] +pub trait Module { + /// Runner function that executes when the subcommand is called + async fn run(agent: &dyn Agent, config: T); + + /// Registering a submodule + async fn register<'a>(agent: &dyn Agent, matches: &ArgMatches<'a>); +} + +/// Registers all the components of the cli +pub async fn register_cli() { + // Load the yaml file containing the cli setup + let yaml = load_yaml!("../../cli.yaml"); + + // Get all the supplied flags and values + let matches = App::from_yaml(yaml) + .version(env!("CARGO_PKG_VERSION")) + .author(env!("CARGO_PKG_AUTHORS")) + .about(env!("CARGO_PKG_DESCRIPTION")) + .get_matches(); + + // create an httpAgent when you supply an endpoint + let agent = match matches.value_of("endpoint") { + Some(endpoint) => HttpAgent::new(endpoint.to_string()), + None => throw(Error::InvalidEndpoint), + }; + + agent.check_endpoint().await; + + // Registering the subcommands and their modules + ConnectionsModule::register(&agent, &matches).await; + InvitationsModule::register(&agent, &matches).await; + CredentialsModule::register(&agent, &matches).await; + MessagesModule::register(&agent, &matches).await; + FeaturesModule::register(&agent, &matches).await; +} diff --git a/src/main.rs b/src/main.rs index b223625e..babb7ed5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,15 +3,12 @@ #![allow(clippy::enum_variant_names)] #![warn(missing_docs, clippy::missing_docs_in_private_items)] +#![feature(iter_zip)] #[macro_use] extern crate clap; -use crate::agent::agents::HttpAgentExtended; -use crate::agent::http_agent::HttpAgent; -use crate::error::{throw, Error}; -use clap::App; -use typing::{ConnectionsConfig, InvitationConfig, MessageConfig}; +use cli::register::register_cli; /// agent mod agent; @@ -31,67 +28,5 @@ mod utils; /// Initializes the application #[tokio::main] async fn main() { - // Load the yaml file containing the cli setup - let yaml = load_yaml!("../cli.yaml"); - - // Get all the supplied flags and values - let matches = App::from_yaml(yaml) - .version(env!("CARGO_PKG_VERSION")) - .author(env!("CARGO_PKG_AUTHORS")) - .about(env!("CARGO_PKG_DESCRIPTION")) - .get_matches(); - - // create an httpAgent when you supply an endpoint - let agent = match matches.value_of("endpoint") { - Some(endpoint) => HttpAgent::new(endpoint), - None => throw(Error::InvalidEndpoint), - }; - - agent.check_endpoint().await; - - // Matches the `feature` subcommand - if matches.subcommand_matches("features").is_some() { - cli::features::run(&agent).await - } - - // Matches the `message` subcommand - if let Some(matches_connections) = matches.subcommand_matches("message") { - // We can use unwrap here because these values are required by the cli - let id = matches_connections.value_of("id").unwrap(); - let message = matches_connections.value_of("message").unwrap(); - - let config = MessageConfig { id, message }; - - cli::message::run(&agent, config).await - } - - // Matches the `connections` subcommand - if let Some(matches_connections) = matches.subcommand_matches("connections") { - let id = matches_connections.value_of("id"); - let alias = matches_connections.value_of("alias"); - - let config = ConnectionsConfig { id, alias }; - - cli::connections::run(&agent, config).await - } - - // Matches the `invite` subcommand - if let Some(matches_invite) = matches.subcommand_matches("invite") { - let auto_accept = matches_invite.is_present("auto-accept"); - let multi_use = matches_invite.is_present("multi-use"); - let alias = matches_invite.value_of("alias"); - let qr = matches_invite.is_present("qr"); - let toolbox = matches_invite.is_present("toolbox"); - - let config = InvitationConfig { - auto_accept, - multi_use, - alias, - qr, - toolbox, - }; - - // create agent and convert config - cli::invite::run(&agent, config).await - } + register_cli().await; } diff --git a/src/typing.rs b/src/typing.rs index 0a977639..c96f1048 100644 --- a/src/typing.rs +++ b/src/typing.rs @@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize}; use serde_json::{Map, Value}; /// Type of the invitation configuration as received by the cli -pub struct InvitationConfig<'a> { +pub struct InvitationConfig { /// Whether the invitation should auto accept pub auto_accept: bool, @@ -10,7 +10,7 @@ pub struct InvitationConfig<'a> { pub multi_use: bool, /// Alias for the connection that will be created with that invitation - pub alias: Option<&'a str>, + pub alias: Option, /// Whether it will print a qr code instead of a url pub qr: bool, @@ -20,21 +20,34 @@ pub struct InvitationConfig<'a> { } /// Type of the connections configuration as received by the cli -pub struct ConnectionsConfig<'a> { +pub struct ConnectionsConfig { /// Filter connections by this alias - pub alias: Option<&'a str>, + pub alias: Option, /// Get a connection by this id - pub id: Option<&'a str>, + pub connection_id: Option, } /// Type of the message configuration as received by the cli -pub struct MessageConfig<'a> { - /// id to send the message to - pub id: &'a str, +pub struct MessageConfig { + /// connection id to send the message to + pub connection_id: String, /// The message to send - pub message: &'a str, + pub message: String, +} + +/// Type of the issue credential configuration as received by the cli +#[derive(Debug, Serialize)] +pub struct IssueCredentialConfig { + /// The connection to send the credential to + pub connection_id: String, + + /// The credential definition used for the credential + pub credential_definition_id: String, + + /// The attributes for the credential + pub attributes: Vec, } /// Type of the received connections list diff --git a/src/utils/logger.rs b/src/utils/logger.rs index 1cda9e43..aaf023e1 100644 --- a/src/utils/logger.rs +++ b/src/utils/logger.rs @@ -1,9 +1,21 @@ use colored::*; +use serde::Serialize; /// Logger struct that allows us access to stdout and stderr pub struct Log; impl Log { + /// json formatted stdout logger + pub fn log_pretty(obj: T) { + let buf = Vec::new(); + let formatter = serde_json::ser::PrettyFormatter::with_indent(b" "); + let mut ser = serde_json::Serializer::with_formatter(buf, formatter); + + obj.serialize(&mut ser).unwrap(); + + Log::log(&String::from_utf8(ser.into_inner()).unwrap()); + } + /// Log messages that contain generic info pub fn log(string: &str) { println!("{}", String::from(string)); From 04bd1fe07b42f6498db865b48c9ca2c7470f2627 Mon Sep 17 00:00:00 2001 From: Berend Sliedrecht Date: Wed, 15 Dec 2021 09:18:06 +0100 Subject: [PATCH 8/9] fix: minor refactor Signed-off-by: Berend Sliedrecht --- src/agent/agents.rs | 4 ++-- src/agent/http_agent.rs | 12 ++++++------ src/utils/http.rs | 26 +++++++++----------------- 3 files changed, 17 insertions(+), 25 deletions(-) diff --git a/src/agent/agents.rs b/src/agent/agents.rs index d57cb6ad..6eef66d2 100644 --- a/src/agent/agents.rs +++ b/src/agent/agents.rs @@ -20,10 +20,10 @@ pub trait Agent { async fn discover_features(&self) -> Features; /// Send a basic message to another agent - async fn send_message(&self, config: &MessageConfig) -> (); + async fn send_message(&self, config: &MessageConfig); /// Offer a credential to another agent - async fn offer_credential(&self, config: &IssueCredentialConfig) -> (); + async fn offer_credential(&self, config: &IssueCredentialConfig); } /// HTTP specific cloudagent functionality diff --git a/src/agent/http_agent.rs b/src/agent/http_agent.rs index 0703d9e7..bf077bee 100644 --- a/src/agent/http_agent.rs +++ b/src/agent/http_agent.rs @@ -121,12 +121,12 @@ impl Agent for HttpAgent { } })); } else { - let multi_use = ("multi_use", config.multi_use.to_string()); - let auto_accept = ("auto_accept", config.auto_accept.to_string()); - - query.push(multi_use); - query.push(auto_accept); - + if config.multi_use { + query.push(("multi_use", true.to_string())); + } + if config.auto_accept { + query.push(("auto_accept", true.to_string())) + } if let Some(alias) = &config.alias { query.push(("alias", alias.to_string())); } diff --git a/src/utils/http.rs b/src/utils/http.rs index 2e4e9946..6d79dd4e 100644 --- a/src/utils/http.rs +++ b/src/utils/http.rs @@ -1,33 +1,20 @@ -use reqwest::{Client, Url}; +use reqwest::{Client, RequestBuilder, Url}; use serde::de::DeserializeOwned; use serde_json::Value; use crate::error::{throw, Error}; -/// Handle calling of any endpoint with get +/// Builds a get request and calls the sender pub async fn get(url: Url, query: Option>) -> T { let client = match query { Some(q) => Client::new().get(url).query(&q), None => Client::new().get(url), }; - let response = client.send().await; - - match response { - Ok(res) => { - if res.status().is_success() { - return match res.json().await { - Ok(parsed) => parsed, - Err(_) => throw(Error::ServerResponseParseError), - }; - } - throw(Error::InternalServerError) - } - Err(_) => throw(Error::InternalServerError), - } + send::(client).await } -/// Handle calling of any endpoint with post +/// Builds a post request and calls the sender pub async fn post( url: Url, query: Option>, @@ -40,6 +27,11 @@ pub async fn post( None => client, }; + send::(client).await +} + +/// Sends any request +async fn send(client: RequestBuilder) -> T { let response = client.send().await; match response { From a9c33052544b3e0abcc487cd3fabfac047575c44 Mon Sep 17 00:00:00 2001 From: Berend Sliedrecht Date: Wed, 15 Dec 2021 09:27:41 +0100 Subject: [PATCH 9/9] refactor: clippy cleanup Signed-off-by: Berend Sliedrecht --- src/cli/register.rs | 12 +++++++++++- src/error.rs | 4 ---- src/utils/config.rs | 17 ++++++----------- 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/src/cli/register.rs b/src/cli/register.rs index 81938601..db021dbb 100644 --- a/src/cli/register.rs +++ b/src/cli/register.rs @@ -6,6 +6,7 @@ use super::message::MessagesModule; use crate::agent::agents::{Agent, HttpAgentExtended}; use crate::agent::http_agent::HttpAgent; use crate::error::{throw, Error}; +use crate::utils::config; use async_trait::async_trait; use clap::{App, ArgMatches}; @@ -31,10 +32,19 @@ pub async fn register_cli() { .about(env!("CARGO_PKG_DESCRIPTION")) .get_matches(); + // Takes a path, but prepends the home directory... kinda sketchy + let endpoint_from_config = config::get_value("/.config/accf/ex.ini", "Default", "endpoint"); + // create an httpAgent when you supply an endpoint let agent = match matches.value_of("endpoint") { Some(endpoint) => HttpAgent::new(endpoint.to_string()), - None => throw(Error::InvalidEndpoint), + None => match endpoint_from_config { + Some(e) => HttpAgent::new(e), + None => match endpoint_from_config { + Some(e) => HttpAgent::new(e), + None => throw(Error::NoSuppliedEndpoint), + }, + }, }; agent.check_endpoint().await; diff --git a/src/error.rs b/src/error.rs index a23e9477..e0de8a6d 100644 --- a/src/error.rs +++ b/src/error.rs @@ -4,9 +4,6 @@ use crate::utils::logger::Log; pub enum Error { /// Did not supply any endpoint in either the config of the OPTION NoSuppliedEndpoint, - - /// Endpoint is incorrect - InvalidEndpoint, /// Response from the server could not be parsed ServerResponseParseError, @@ -19,7 +16,6 @@ pub enum Error { pub fn throw(error: Error) -> ! { match error { Error::NoSuppliedEndpoint => Log::error("No Endpoint Supplied"), - Error::InvalidEndpoint => Log::error("Invalid Endpoint"), Error::ServerResponseParseError => Log::error("Unable to parse response from server"), Error::InternalServerError => Log::error("Internal Server Error"), } diff --git a/src/utils/config.rs b/src/utils/config.rs index 8f76b3b4..996a5e54 100644 --- a/src/utils/config.rs +++ b/src/utils/config.rs @@ -1,7 +1,7 @@ use ini::{Ini, Properties}; use std::env; -// Load a config file and ignore errors as we will just fall back on the option provided +/// Load a config file and ignore errors as we will just fall back on the option provided fn load(path: &str) -> Option { let cfg = Ini::load_from_file(path); @@ -11,22 +11,17 @@ fn load(path: &str) -> Option { } } +/// Get a section in the config file fn get_section(key: &str, cfg: &Ini) -> Option { - let section = cfg.section(Some(key)); - - match section { - Some(s) => Some(s.to_owned()), - None => None, - } + cfg.section(Some(key)).map(|s| s.to_owned()) } +/// Get a value by key in the section fn get_value_by_key(key: &str, prop: &Properties) -> Option { - match prop.get(key) { - Some(v) => Some(String::from(v)), - None => None, - } + prop.get(key).map(String::from) } +/// Get a value by path, section and key pub fn get_value(path: &str, section: &str, key: &str) -> Option { let home = env::var("HOME");