diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 5357f2c9b..e97e6ac0e 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -19,6 +19,7 @@ toml = "0.5" dirs = "3.0" yaml-rust = "0.3" colored = "2" +blake3 = "1.2" [build-dependencies] tonic-build = { version = "0.6", features = ["prost", "rustfmt"] } diff --git a/cli/build.rs b/cli/build.rs index 76a9af116..bd846e35e 100644 --- a/cli/build.rs +++ b/cli/build.rs @@ -8,6 +8,10 @@ fn main() { config .compile_well_known_types(true) + .type_attribute( + "WalletProfile", + "#[derive(::serde::Serialize, ::serde::Deserialize, Copy)]", + ) .type_attribute( "JsonPayload", "#[derive(::serde::Serialize, ::serde::Deserialize)]", diff --git a/cli/src/cli.yaml b/cli/src/cli.yaml index 9c5182e20..b16ed0d71 100644 --- a/cli/src/cli.yaml +++ b/cli/src/cli.yaml @@ -8,138 +8,6 @@ about: >- ┴ ┴└─┴┘└┘└─┘┴└─┘ subcommands: - - didkey: - about: didkey commands - subcommands: - - generate: - about: Generates a Json Web Key - version: "0.1" - args: - - out: - long: out - value_name: STRING - help: File to save Json Web Key in - takes_value: true - - kty: - long: kty - value_name: STRING - help: >- - Specify key type. Defaults to X25519. Options are Ed25519, - X25519, P256, Bls12381_G2, and secp256k1. - takes_value: true - possible_values: - - Ed25519 - - X25519 - - P-256 - - Bls12381_G2 - - Secp256k1 - - resolve: - about: Resolve a DID - version: "0.1" - args: - - uri: - value_name: STRING - help: DID URI to resolve - takes_value: true - required: true - - didcomm: - about: didcomm commands - subcommands: - - pack: - about: Packs a plaintext message - version: "0.1" - short: p - args: - - sender_key: - value_name: FILE - help: Sender's Json Web Key - takes_value: true - required: true - - receiver_key: - value_name: FILE - help: Receiver's Json Web Key - takes_value: true - required: true - - associated_data: - value_name: FILE - help: Associated data to be packed - takes_value: true - long: data - - plaintext: - value_name: FILE - help: Plaintext message to be packed - takes_value: true - long: text - - encryption_mode: - value_name: STRING - long: mode - help: >- - Encryption mode. Default is direct. Options are direct and - content_encryption_key - possible_values: - - direct - - content_encryption_key - - encryption_algorithm: - value_name: STRING - long: alg - help: >- - Encryption algorithm. Default is xchacha20poly1305. Options - are xchacha20poly1305 and aes_gcm - possible_values: - - xchacha20poly1305 - - aes_gcm - - out: - long: out - value_name: FILE - help: output file for your packed message - - unpack: - about: Unpacks an encrypted message - version: "0.1" - short: up - args: - - sender_key: - value_name: FILE - help: Sender's Json Web Key - takes_value: true - - receiver_key: - value_name: FILE - help: Receiver's Json Web Key - takes_value: true - - encrypted_message: - value_name: FILE - help: Encrypted message to be unpacked - takes_value: true - - verify: - about: Verify a signed message - version: "0.1" - short: v - args: - - key: - value_name: FILE - help: Recepient's Json Web Key - takes_value: true - - signed_message: - value_name: FILE - help: Signed message to be verified - takes_value: true - - sign: - about: Sign a message - version: "0.1" - args: - - key: - value_name: FILE - help: Signer's Json Web Key - takes_value: true - required: true - - payload: - value_name: FILE - help: Bytes to be signed - takes_value: true - long: payload - - out: - long: out - value_name: FILE - help: output file for your packed message - config: about: Commands to set configuration parameters args: @@ -165,9 +33,6 @@ subcommands: - wallet: about: Wallet Service subcommands: - - provider-configuration: - about: Get the provider configuration - version: "0.1" - create: about: Create a new wallet version: "0.1" diff --git a/cli/src/main.rs b/cli/src/main.rs index ff50c9a3b..60b4493b0 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -5,7 +5,7 @@ pub mod services; extern crate clap; use clap::{App, ArgMatches}; use parser::Service; -use services::config::Config; +use services::config::DefaultConfig; use yaml_rust::Yaml; #[allow(unused_must_use)] @@ -17,7 +17,7 @@ fn main() { } fn process(yaml: &Yaml, matches: ArgMatches) { - let config = Config::from(&matches); + let config = DefaultConfig::from(&matches); let service = parser::parse(&matches); if service == Service::Unknown { diff --git a/cli/src/parser/config.rs b/cli/src/parser/config.rs index 9f6fd0308..94e2ee52e 100644 --- a/cli/src/parser/config.rs +++ b/cli/src/parser/config.rs @@ -1,6 +1,6 @@ use clap::ArgMatches; -use crate::services::config::Config; +use crate::services::config::DefaultConfig; #[derive(Debug, PartialEq, Default)] pub struct Command<'a> { @@ -27,7 +27,7 @@ pub fn parse<'a>(args: &'a ArgMatches<'_>) -> Command<'a> { command.server.address = args.value_of("server-address") } if args.is_present("show") { - Config::init().unwrap().print().unwrap(); + DefaultConfig::init().unwrap().print().unwrap(); } command diff --git a/cli/src/proto/services/universalwallet/v1/mod.rs b/cli/src/proto/services/universalwallet/v1/mod.rs index df6aab6ef..94bcc7f38 100644 --- a/cli/src/proto/services/universalwallet/v1/mod.rs +++ b/cli/src/proto/services/universalwallet/v1/mod.rs @@ -69,7 +69,7 @@ pub mod invitation_token { ///Stores profile data for accessing a wallet. ///This result should be stored somewhere safe, ///as it contains private key information. -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(::serde::Serialize, ::serde::Deserialize, Copy, Clone, PartialEq, ::prost::Message)] pub struct WalletProfile { #[prost(string, tag = "1")] pub name: ::prost::alloc::string::String, diff --git a/cli/src/services/config.rs b/cli/src/services/config.rs index d6cab96a4..8a9907321 100644 --- a/cli/src/services/config.rs +++ b/cli/src/services/config.rs @@ -1,22 +1,26 @@ use clap::ArgMatches; +use okapi::{proto::security::CreateOberonProofRequest, Oberon}; use serde::{Deserialize, Serialize}; +use std::time::{SystemTime, UNIX_EPOCH}; use std::{env::var, path::Path}; use std::{fs, io::prelude::*}; use std::{fs::OpenOptions, path::PathBuf}; use tonic::service::Interceptor; -use trinsic::proto::services::universalwallet::v1::WalletProfile; -use trinsic::MessageFormatter; +use trinsic::{ + proto::services::{common::v1::Nonce, universalwallet::v1::WalletProfile}, + MessageFormatter, +}; use crate::parser::config::{Command, ProfileArgs, ServerArgs}; -pub(crate) static DEFAULT_SERVER_ADDRESS: &str = "http://localhost:5000/"; +pub(crate) static DEFAULT_SERVER_ADDRESS: &str = "https://prod.trinsic.cloud:443/"; #[cfg(not(test))] pub static CONFIG_FILENAME: &str = "config.toml"; #[cfg(test)] pub static CONFIG_FILENAME: &str = "config.test.toml"; #[derive(Debug, Default, PartialEq, Clone, Serialize, Deserialize)] -pub(crate) struct Config { +pub(crate) struct DefaultConfig { pub server: ConfigServer, pub profile: Option, } @@ -70,22 +74,22 @@ impl From for Error { } } -impl From<&ArgMatches<'_>> for Config { +impl From<&ArgMatches<'_>> for DefaultConfig { fn from(matches: &ArgMatches<'_>) -> Self { if matches.is_present("profile") { - Config { + DefaultConfig { profile: Some(ConfigProfile { default: matches.value_of("profile").unwrap().to_string(), }), - ..Config::init().unwrap() + ..DefaultConfig::init().unwrap() } } else { - Config::init().unwrap() + DefaultConfig::init().unwrap() } } } -impl Config { +impl DefaultConfig { /// Initialize the configuration by reading the default confgiruation file. /// If no file is found, a new one will be created with default options. pub(crate) fn init() -> Result { @@ -94,13 +98,13 @@ impl Config { // If a default file is not found, create one with default configuration if !Path::new(&config_file).exists() { - create_file(&config_file, &Config::default())?; + create_file(&config_file, &DefaultConfig::default())?; }; let mut buffer = String::new(); let mut file = OpenOptions::new().read(true).open(&config_file)?; file.read_to_string(&mut buffer)?; - let config: Config = toml::from_str(&buffer)?; + let config: DefaultConfig = toml::from_str(&buffer)?; Ok(config) } @@ -124,16 +128,6 @@ impl Config { T::from_vec(&buffer).map_err(|_| Error::SerializationError) } - pub fn read_capability(&self) -> Result { - let filename = data_path().join(format!("{}.json", self.profile.as_ref().unwrap().default)); - let mut file = OpenOptions::new().read(true).open(filename)?; - - let mut buffer: String = String::new(); - file.read_to_string(&mut buffer)?; - - Ok(buffer) - } - pub fn save_profile( &mut self, profile: WalletProfile, @@ -166,42 +160,6 @@ impl Config { } self.save_config()?; - self.save_capability(&profile, name)?; - - Ok(()) - } - - fn save_capability(&self, profile: &WalletProfile, name: &str) -> Result<(), Error> { - // Save capability invocation generated from the input profile - use okapi::{ - proto::{keys::*, proofs::*}, - *, - }; - use trinsic::utils::*; - let capability_filename = data_path().join(format!("{}.json", name)); - let capability_document = get_capability_document(&profile.wallet_id); - - let res = LdProofs::create_proof(&CreateProofRequest { - key: Some(JsonWebKey::from_vec(&profile.invoker_jwk).expect("Invalid key")), - document: Some( - serde_json::from_str(&capability_document).expect("Invalid capability document"), - ), - suite: LdSuite::Jcsed25519signature2020 as i32, - }) - .expect("Error creating proof"); - - let cap_invocation = base64::encode( - serde_json::to_string(&res.signed_document) - .expect("Unable to serialize signed document as json"), - ); - let mut caps_file = OpenOptions::new() - .create(true) - .write(true) - .truncate(true) - .open(capability_filename)?; - - caps_file.write_all(&cap_invocation.as_bytes().to_vec())?; - caps_file.flush()?; Ok(()) } @@ -237,17 +195,46 @@ impl Config { // } // } -impl Interceptor for Config { +impl Interceptor for DefaultConfig { fn call( &mut self, mut request: tonic::Request<()>, ) -> Result, tonic::Status> { + // read the currently configured profile + let profile: WalletProfile = self.read_profile().unwrap(); + + // generate nonce by combining the current unix epoch timestam + // and a hash of the request payload + let nonce = Nonce { + timestamp: SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_millis() as i64, + request_hash: blake3::hash(&request.get_ref().to_vec()) + .as_bytes() + .to_vec(), + }; + + // generate proof of knowledge using the stored token and the generated nonce + let proof = Oberon::proof(&CreateOberonProofRequest { + data: profile.auth_data, + token: profile.auth_token, + nonce: nonce.to_vec(), + blinding: vec![], + }) + .unwrap(); + + // append auhtorization header request.metadata_mut().insert( - "capability-invocation", - self.read_capability() - .expect("couldn't read capability document") - .parse() - .expect("error parsing capability"), + "authorization", + format!( + "Oberon data={data},proof={proof},nonce={nonce},ver=1", + data = base64::encode_config(profile.auth_data, base64::URL_SAFE_NO_PAD), + proof = base64::encode_config(proof.proof, base64::URL_SAFE_NO_PAD), + nonce = base64::encode_config(nonce.to_vec(), base64::URL_SAFE_NO_PAD) + ) + .parse() + .unwrap(), ); Ok(request) } @@ -261,7 +248,7 @@ pub fn execute(args: &Command) { fn set_profile_attr(_args: &ProfileArgs) {} fn set_server_attr(args: &ServerArgs) { - let mut config = Config::init().unwrap(); + let mut config = DefaultConfig::init().unwrap(); if args.address.is_some() { config.server.address = args.address.unwrap().to_string(); } @@ -283,7 +270,7 @@ fn data_path() -> PathBuf { path } -fn create_file(config_dir: &Path, config: &Config) -> Result<(), Error> { +fn create_file(config_dir: &Path, config: &DefaultConfig) -> Result<(), Error> { let mut file = OpenOptions::new() .create_new(true) .read(true) @@ -298,7 +285,7 @@ fn create_file(config_dir: &Path, config: &Config) -> Result<(), Error> { Ok(()) } -fn update_file(config_dir: &Path, config: &Config) -> Result<(), Error> { +fn update_file(config_dir: &Path, config: &DefaultConfig) -> Result<(), Error> { let mut file = OpenOptions::new() .truncate(true) .read(true) @@ -319,7 +306,7 @@ mod test { #[test] fn open_default_config() { - let config = Config::init(); + let config = DefaultConfig::init(); assert!(matches!(config, Ok(_))); } diff --git a/cli/src/services/issuer.rs b/cli/src/services/issuer.rs index a8c9ed434..901d4abd3 100644 --- a/cli/src/services/issuer.rs +++ b/cli/src/services/issuer.rs @@ -5,11 +5,11 @@ use trinsic::proto::services::verifiablecredentials::v1::{ use trinsic::utils::{read_file_as_string, write_file}; use trinsic::{grpc_client_with_auth, *}; -use super::{super::parser::issuer::*, config::Config}; +use super::{super::parser::issuer::*, config::DefaultConfig}; use okapi::MessageFormatter; use tonic::transport::Channel; -pub(crate) fn execute(args: &Command, config: Config) { +pub(crate) fn execute(args: &Command, config: DefaultConfig) { match args { Command::Issue(args) => issue(args, config), Command::CreateProof(args) => create_proof(args, config), @@ -18,7 +18,7 @@ pub(crate) fn execute(args: &Command, config: Config) { } #[tokio::main] -async fn issue(args: &IssueArgs, config: Config) { +async fn issue(args: &IssueArgs, config: DefaultConfig) { let document: okapi::proto::google_protobuf::Struct = serde_json::from_str(&read_file_as_string(args.document)).expect("Unable to parse Item"); let document = document.to_vec(); @@ -49,7 +49,7 @@ async fn issue(args: &IssueArgs, config: Config) { } #[tokio::main] -async fn create_proof(args: &CreateProofArgs, config: Config) { +async fn create_proof(args: &CreateProofArgs, config: DefaultConfig) { let document_id = match args.document_id { Some(id) => id.to_string(), None => panic!("Please include document id"), @@ -86,7 +86,7 @@ async fn create_proof(args: &CreateProofArgs, config: Config) { } #[tokio::main] -async fn verify_proof(args: &VerifyProofArgs, config: Config) { +async fn verify_proof(args: &VerifyProofArgs, config: DefaultConfig) { let document: okapi::proto::google_protobuf::Struct = serde_json::from_str(&read_file_as_string(args.proof_document)) .expect("Unable to parse Item"); diff --git a/cli/src/services/mod.rs b/cli/src/services/mod.rs index 15777f643..c096ca3b5 100644 --- a/cli/src/services/mod.rs +++ b/cli/src/services/mod.rs @@ -6,10 +6,10 @@ mod provider; mod trustregistry; mod wallet; -use self::config::Config; +use self::config::DefaultConfig; use super::parser::Service; -pub(crate) fn execute(args: &Service, config: Config) { +pub(crate) fn execute(args: &Service, config: DefaultConfig) { match args { Service::Wallet(args) => wallet::execute(&args, config).unwrap(), Service::DIDKey(args) => didkey::execute(&args), diff --git a/cli/src/services/provider.rs b/cli/src/services/provider.rs index e8d2e208a..d5376e9c0 100644 --- a/cli/src/services/provider.rs +++ b/cli/src/services/provider.rs @@ -7,7 +7,7 @@ use trinsic::proto::services::provider::v1::{provider_client::ProviderClient, In use trinsic::*; #[allow(clippy::unit_arg)] -pub(crate) fn execute(args: &Command, config: Config) -> Result<(), Error> { +pub(crate) fn execute(args: &Command, config: DefaultConfig) -> Result<(), Error> { match args { Command::Invite(args) => Ok(invite(args, config)), _ => Err(Error::UnknownCommand), @@ -15,7 +15,7 @@ pub(crate) fn execute(args: &Command, config: Config) -> Result<(), Error> { } #[tokio::main] -async fn invite(args: &InviteArgs, config: Config) { +async fn invite(args: &InviteArgs, config: DefaultConfig) { let mut client = grpc_client_with_auth!(ProviderClient, config); let request = tonic::Request::new(InviteRequest { diff --git a/cli/src/services/trustregistry.rs b/cli/src/services/trustregistry.rs index deb15a1e8..2b22c72bb 100644 --- a/cli/src/services/trustregistry.rs +++ b/cli/src/services/trustregistry.rs @@ -7,9 +7,9 @@ use trinsic::{ proto::services::trustregistry::v1::{trust_registry_client::TrustRegistryClient, *}, }; -use super::config::{Config, Error}; +use super::config::{DefaultConfig, Error}; -pub(crate) fn execute(args: &Command, config: &Config) -> Result<(), Error> { +pub(crate) fn execute(args: &Command, config: &DefaultConfig) -> Result<(), Error> { match args { Command::Search(args) => Ok(search(args, config)), Command::RegisterIssuer(args) => Ok(register_issuer(args, config)), @@ -22,7 +22,7 @@ pub(crate) fn execute(args: &Command, config: &Config) -> Result<(), Error> { } #[tokio::main] -async fn search(args: &SearchArgs, config: &Config) { +async fn search(args: &SearchArgs, config: &DefaultConfig) { let query = args .query .as_ref() @@ -45,7 +45,7 @@ async fn search(args: &SearchArgs, config: &Config) { } #[tokio::main] -async fn register_issuer(args: &RegistrationArgs, config: &Config) { +async fn register_issuer(args: &RegistrationArgs, config: &DefaultConfig) { let mut client = grpc_client_with_auth!(TrustRegistryClient, config.to_owned()); let request = tonic::Request::new(RegisterIssuerRequest { @@ -73,7 +73,7 @@ async fn register_issuer(args: &RegistrationArgs, config: &Config) { } #[tokio::main] -async fn check_issuer(args: &RegistrationArgs, config: &Config) { +async fn check_issuer(args: &RegistrationArgs, config: &DefaultConfig) { let mut client = grpc_client_with_auth!(TrustRegistryClient, config.to_owned()); let request = tonic::Request::new(CheckIssuerStatusRequest { @@ -105,7 +105,7 @@ async fn check_issuer(args: &RegistrationArgs, config: &Config) { } #[tokio::main] -async fn check_verifier(args: &RegistrationArgs, config: &Config) { +async fn check_verifier(args: &RegistrationArgs, config: &DefaultConfig) { let mut client = grpc_client_with_auth!(TrustRegistryClient, config.to_owned()); let request = tonic::Request::new(CheckVerifierStatusRequest { @@ -140,7 +140,7 @@ async fn check_verifier(args: &RegistrationArgs, config: &Config) { } #[tokio::main] -async fn unregister_issuer(args: &RegistrationArgs, config: &Config) { +async fn unregister_issuer(args: &RegistrationArgs, config: &DefaultConfig) { let mut client = grpc_client_with_auth!(TrustRegistryClient, config.to_owned()); let request = tonic::Request::new(UnregisterIssuerRequest { @@ -168,7 +168,7 @@ async fn unregister_issuer(args: &RegistrationArgs, config: &Config) { } #[tokio::main] -async fn unregister_verifier(args: &RegistrationArgs, config: &Config) { +async fn unregister_verifier(args: &RegistrationArgs, config: &DefaultConfig) { let mut client = grpc_client_with_auth!(TrustRegistryClient, config.to_owned()); let request = tonic::Request::new(UnregisterVerifierRequest { @@ -199,7 +199,7 @@ async fn unregister_verifier(args: &RegistrationArgs, config: &Config) { } #[tokio::main] -async fn register_verifier(args: &RegistrationArgs, config: &Config) { +async fn register_verifier(args: &RegistrationArgs, config: &DefaultConfig) { let mut client = grpc_client_with_auth!(TrustRegistryClient, config.to_owned()); let request = tonic::Request::new(RegisterVerifierRequest { diff --git a/cli/src/services/wallet.rs b/cli/src/services/wallet.rs index 27cccbf47..eba8065fe 100644 --- a/cli/src/services/wallet.rs +++ b/cli/src/services/wallet.rs @@ -1,10 +1,13 @@ +use std::default::*; + use super::super::parser::wallet::*; use crate::services::config::*; use okapi::{proto::keys::*, DIDKey, MessageFormatter}; +use tonic::codegen::Body; use tonic::transport::Channel; use trinsic::proto::google::protobuf::Struct; use trinsic::proto::services::common::v1::json_payload::Json; -use trinsic::proto::services::common::v1::JsonPayload; +use trinsic::proto::services::common::v1::{JsonPayload, ServerConfig}; use trinsic::proto::services::universalwallet::v1::{ wallet_client::WalletClient, CreateWalletRequest, InsertItemRequest, SearchRequest, WalletProfile, @@ -15,7 +18,7 @@ use trinsic::proto::services::verifiablecredentials::v1::SendRequest; use trinsic::{proto::services::universalwallet::v1::*, utils::read_file_as_string}; #[allow(clippy::unit_arg)] -pub(crate) fn execute(args: &Command, config: Config) -> Result<(), Error> { +pub(crate) fn execute(args: &Command, config: DefaultConfig) -> Result<(), Error> { match args { Command::Create(args) => create(args, config), Command::Search(args) => Ok(search(args, config)), @@ -27,7 +30,7 @@ pub(crate) fn execute(args: &Command, config: Config) -> Result<(), Error> { } #[tokio::main] -async fn get_provider_configuration(config: Config) { +async fn get_provider_configuration(config: DefaultConfig) { let mut client = WalletClient::connect(config.server.address) .await .expect("Unable to connect to server"); @@ -43,25 +46,12 @@ async fn get_provider_configuration(config: Config) { } #[tokio::main] -async fn create(args: &CreateArgs, config: Config) -> Result<(), Error> { +async fn create(args: &CreateArgs, config: DefaultConfig) -> Result<(), Error> { let mut new_config = config.clone(); - let key = match &args.key { - Some(filename) => { - serde_json::from_str(&read_file_as_string(Some(filename))).expect("Unable to parse key") - } - None => DIDKey::generate(&GenerateKeyRequest { - seed: vec![], - key_type: KeyType::Ed25519 as i32, - }) - .unwrap(), - }; - - let did_doc_bytes = &key.did_document.unwrap().to_vec(); - let description = match &args.description { Some(desc) => desc.to_string(), - None => "My Cloud Wallet".to_string(), + None => "New Wallet (from CLI)".to_string(), }; let channel = Channel::from_shared(config.server.address) @@ -73,7 +63,6 @@ async fn create(args: &CreateArgs, config: Config) -> Result<(), Error> { let mut client = WalletClient::new(channel); let request = tonic::Request::new(CreateWalletRequest { - controller: key.key[0].kid.clone(), description, security_code: args .security_code @@ -87,22 +76,21 @@ async fn create(args: &CreateArgs, config: Config) -> Result<(), Error> { .expect("Create Wallet failed") .into_inner(); - use trinsic::MessageFormatter; let profile = WalletProfile { - wallet_id: response.wallet_id, - did_document: Some(JsonPayload { - json: Some(Json::JsonStruct(Struct::from_vec(&did_doc_bytes).unwrap())), + name: args.profile_name.map_or(String::new(), |x| x.to_string()), + auth_data: response.auth_data, + auth_token: response.auth_token, + is_protected: response.is_protected, + config: Some(ServerConfig { + ..Default::default() }), - invoker: key.key[0].kid.clone(), - invoker_jwk: key.key[0].to_vec(), - capability: response.capability, }; new_config.save_profile(profile, args.profile_name.unwrap(), args.set_default) } #[tokio::main] -async fn search(args: &SearchArgs, config: Config) { +async fn search(args: &SearchArgs, config: DefaultConfig) { let query = args .query .map_or("SELECT * FROM c".to_string(), |q| q.to_string()); @@ -136,7 +124,7 @@ async fn search(args: &SearchArgs, config: Config) { } #[tokio::main] -async fn insert_item(args: &InsertItemArgs, config: Config) { +async fn insert_item(args: &InsertItemArgs, config: DefaultConfig) { let item: Struct = serde_json::from_str(&read_file_as_string(args.item)).expect("Unable to parse Item"); let item_bytes = item.to_vec(); @@ -168,7 +156,7 @@ async fn insert_item(args: &InsertItemArgs, config: Config) { } #[tokio::main] -async fn send(args: &SendArgs, config: Config) { +async fn send(args: &SendArgs, config: DefaultConfig) { let item: okapi::proto::google_protobuf::Struct = serde_json::from_str(&read_file_as_string(args.item)).expect("Unable to parse Item"); let item_bytes = item.to_vec();