refactor: move tools/upend_cli functionality to the cli crate

feat/type-attributes
Tomáš Mládek 2023-04-25 19:27:31 +02:00
parent a724d4c07b
commit 78ba02bdc4
9 changed files with 332 additions and 1991 deletions

51
Cargo.lock generated
View File

@ -430,9 +430,9 @@ dependencies = [
[[package]]
name = "anstream"
version = "0.3.0"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e579a7752471abc2a8268df8b20005e3eadd975f585398f17efcfd8d4927371"
checksum = "6342bd4f5a1205d7f41e94a41a901f5647c938cdfa96036338e8533c9d6c2450"
dependencies = [
"anstyle",
"anstyle-parse",
@ -469,9 +469,9 @@ dependencies = [
[[package]]
name = "anstyle-wincon"
version = "1.0.0"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bcd8291a340dd8ac70e18878bc4501dd7b4ff970cfa21c207d36ece51ea88fd"
checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188"
dependencies = [
"anstyle",
"windows-sys 0.48.0",
@ -1136,19 +1136,6 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "env_logger"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0"
dependencies = [
"humantime",
"is-terminal",
"log",
"regex",
"termcolor",
]
[[package]]
name = "errno"
version = "0.3.1"
@ -1542,12 +1529,6 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421"
[[package]]
name = "humantime"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
name = "hyper"
version = "0.14.26"
@ -3654,13 +3635,13 @@ dependencies = [
[[package]]
name = "tracing-attributes"
version = "0.1.23"
version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a"
checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
"syn 2.0.15",
]
[[package]]
@ -3942,6 +3923,7 @@ dependencies = [
"rand 0.8.5",
"rayon",
"regex",
"reqwest",
"serde",
"serde_json",
"shadow-rs",
@ -3958,23 +3940,6 @@ dependencies = [
"webpage",
]
[[package]]
name = "upend_cli"
version = "0.1.0"
dependencies = [
"anyhow",
"clap",
"env_logger",
"filebuffer",
"log",
"multibase",
"multihash",
"reqwest",
"serde",
"serde_json",
"uuid",
]
[[package]]
name = "url"
version = "2.3.1"

View File

@ -10,7 +10,7 @@ edition = "2018"
build = "build.rs"
[workspace]
members = ["cli", "tools/upend_cli"]
members = ["cli"]
[dependencies]
log = "0.4"

View File

@ -79,6 +79,7 @@ id3 = { version = "1.0.2", optional = true }
kamadak-exif = { version = "0.5.4", optional = true }
shadow-rs = "0.17"
reqwest = { version = "0.11.16", features = ["blocking", "json"] }
[build-dependencies]
shadow-rs = "0.17"

View File

@ -1,17 +1,23 @@
#[macro_use]
extern crate upend;
use std::net::SocketAddr;
use std::path::PathBuf;
use actix_cors::Cors;
use actix_web::{middleware, App, HttpServer};
use anyhow::anyhow;
use anyhow::Result;
use clap::Parser;
use clap::{Args, Parser, Subcommand, ValueEnum};
use filebuffer::FileBuffer;
use rand::{thread_rng, Rng};
use reqwest::Url;
use serde_json::json;
use std::net::SocketAddr;
use std::path::PathBuf;
use std::sync::Arc;
use tracing::{debug, info, warn};
use tracing::trace;
use tracing::{debug, error, info, warn};
use tracing_subscriber::filter::{EnvFilter, LevelFilter};
use upend::addressing::Address;
use upend::database::entry::EntryValue;
use upend::util::hash::hash;
use upend::{
common::{build, get_static_dir},
@ -31,9 +37,72 @@ mod util;
mod extractors;
mod previews;
#[derive(Parser, Debug)]
#[command(name="upend", author, version)]
struct Args {
#[derive(Debug, Parser)]
#[command(name = "upend", author, version)]
struct Cli {
#[command(subcommand)]
command: Commands,
}
#[derive(Debug, Subcommand)]
enum Commands {
/// Perform a query against an UpEnd server instance.
Query {
/// URL of the UpEnd instance to query.
#[arg(short, long, default_value="http://localhost:8093")]
url: Url,
/// The query itself, in L-expression format.
query: String,
/// Output format
#[arg(short, long, default_value = "tsv")]
format: OutputFormat,
},
/// Insert an entry.
Insert {
/// URL of the UpEnd instance to query.
#[arg(short, long)]
url: Option<Url>,
/// The address of the entity.
entity: String,
// The attribute.
attribute: String,
// The value.
value: EntryValue,
/// Output format
#[arg(short, long, default_value = "tsv")]
format: OutputFormat,
},
/// Get the address of a file, attribute or URL.
Address {
/// Type of input to be addressed
_type: AddressType,
/// Path to a file, hash...
input: String,
/// Output format
#[arg(short, long, default_value = "tsv")]
format: OutputFormat,
},
/// Start an UpEnd server instance.
Serve(ServeArgs),
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, ValueEnum)]
enum OutputFormat {
Json,
Tsv,
Raw,
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, ValueEnum)]
enum AddressType {
/// Hash a file and output its address.
File,
/// Compute an address from the output of `sha256sum`
Sha256sum,
}
#[derive(Debug, Args)]
struct ServeArgs {
/// Directory to serve a vault from.
#[arg()]
directory: PathBuf,
@ -92,7 +161,7 @@ struct Args {
}
fn main() -> Result<()> {
let args = Args::parse();
let args = Cli::parse();
tracing_subscriber::fmt()
.with_env_filter(
@ -102,189 +171,272 @@ fn main() -> Result<()> {
)
.init();
info!("Starting UpEnd {}...", build::PKG_VERSION);
let sys = actix::System::new("upend");
match args.command {
Commands::Query { url, query, format } => {
let api_url = url.join("/api/query")?;
let job_container = JobContainer::new();
debug!("Querying \"{}\"", api_url);
let client = reqwest::blocking::Client::new();
let response = client.post(api_url).body(query).send()?;
let vault_path = args.directory;
let open_result =
UpEndDatabase::open(&vault_path, args.reinitialize).expect("failed to open database!");
let upend = Arc::new(open_result.db);
let store = Arc::new(Box::new(
FsStore::from_path(args.store_path.unwrap_or_else(|| vault_path.clone())).unwrap(),
) as Box<dyn UpStore + Send + Sync>);
let ui_path = get_static_dir("webui");
if ui_path.is_err() {
warn!(
"Couldn't locate Web UI directory ({:?}), disabling...",
ui_path
);
}
let ui_enabled = ui_path.is_ok() && !args.no_ui;
let browser_enabled = !args.no_desktop && ui_enabled && !args.no_browser;
let preview_path = upend.path.join("previews");
#[cfg(feature = "previews")]
let preview_store = Some(Arc::new(crate::previews::PreviewStore::new(
preview_path.clone(),
store.clone(),
)));
#[cfg(feature = "previews")]
let preview_pool = Some(Arc::new(
rayon::ThreadPoolBuilder::new()
.num_threads(num_cpus::get() / 2)
.build()
.unwrap(),
));
if args.clean {
info!("Cleaning temporary directories...");
if preview_path.exists() {
std::fs::remove_dir_all(&preview_path).unwrap();
debug!("Removed {preview_path:?}");
} else {
debug!("No preview path exists, continuing...");
match response.error_for_status_ref() {
Ok(_) => match format {
OutputFormat::Json | OutputFormat::Raw => Ok(println!("{}", response.text()?)),
OutputFormat::Tsv => todo!(),
},
Err(err) => {
error!("{}", response.text()?);
return Err(err.into());
}
}
}
}
Commands::Insert {
url,
entity,
attribute,
value,
format: _,
} => {
let url = url.unwrap_or("http://localhost:8093".parse().unwrap());
let api_url = url.join("/api/obj")?;
#[cfg(not(feature = "previews"))]
let preview_store = None;
#[cfg(not(feature = "previews"))]
let preview_pool = None;
if let EntryValue::Invalid = value {
return Err(anyhow!("Invalid entry value."));
}
let mut bind: SocketAddr = args.bind.parse().expect("Incorrect bind format.");
let body = json!({
"entity": entity,
"attribute": attribute,
"value": value
});
let secret = args.secret.unwrap_or_else(|| {
warn!("No secret supplied, generating one at random.");
debug!("Inserting {:?} at \"{}\"", body, api_url);
let client = reqwest::blocking::Client::new();
let response = client.put(api_url).json(&body).send()?;
thread_rng()
.sample_iter(&rand::distributions::Alphanumeric)
.take(32)
.map(char::from)
.collect()
});
match response.error_for_status_ref() {
Ok(_) => {
let data: Vec<String> = response.json()?;
Ok(println!("{}", data[0]))
}
Err(err) => {
error!("{}", response.text()?);
return Err(err.into());
}
}
}
Commands::Address {
_type,
input,
format,
} => {
let address = match _type {
AddressType::File => {
let filepath = PathBuf::from(input);
debug!("Hashing {:?}...", filepath);
let fbuffer = FileBuffer::open(&filepath)?;
let digest = hash(&fbuffer);
trace!("Finished hashing {:?}...", &filepath);
Address::Hash(digest)
}
AddressType::Sha256sum => {
let digest = multibase::Base::Base16Lower.decode(input)?;
Address::Hash(upend::util::hash::Hash(digest))
}
};
let state = routes::State {
upend: upend.clone(),
store,
job_container: job_container.clone(),
preview_store,
preview_pool,
config: UpEndConfig {
vault_name: Some(args.vault_name.unwrap_or_else(|| {
vault_path
.iter()
.last()
.unwrap()
.to_string_lossy()
.into_owned()
})),
desktop_enabled: !args.no_desktop,
trust_executables: args.trust_executables,
key: args.key,
secret,
},
};
match format {
OutputFormat::Json => Ok(println!("\"{}\"", address)),
OutputFormat::Tsv | OutputFormat::Raw => Ok(println!("{}", address)),
}
}
Commands::Serve(args) => {
info!("Starting UpEnd {}...", build::PKG_VERSION);
let sys = actix::System::new("upend");
// Start HTTP server
let job_container = JobContainer::new();
let mut cnt = 0;
let ui_path = ui_path.ok();
let server = loop {
let state = state.clone();
let ui_path = ui_path.clone();
let allowed_origins = args.allow_host.clone();
let vault_path = args.directory;
let server = HttpServer::new(move || {
let allowed_origins = allowed_origins.clone();
let open_result = UpEndDatabase::open(&vault_path, args.reinitialize)
.expect("failed to open database!");
let cors = Cors::default()
.allowed_origin("http://localhost")
.allowed_origin_fn(|origin, _req_head| {
origin.as_bytes().starts_with(b"http://localhost:")
})
.allowed_origin_fn(move |origin, _req_head| {
allowed_origins
.iter()
.any(|allowed_origin| *allowed_origin == "*" || origin == allowed_origin)
})
.allow_any_method();
let upend = Arc::new(open_result.db);
let store = Arc::new(Box::new(
FsStore::from_path(args.store_path.unwrap_or_else(|| vault_path.clone())).unwrap(),
) as Box<dyn UpStore + Send + Sync>);
let app = App::new()
.wrap(cors)
.app_data(actix_web::web::PayloadConfig::new(4_294_967_296))
.data(state.clone())
.wrap(middleware::Logger::default().exclude("/api/jobs"))
.service(routes::login)
.service(routes::get_raw)
.service(routes::get_thumbnail)
.service(routes::get_query)
.service(routes::get_object)
.service(routes::put_object)
.service(routes::put_blob)
.service(routes::put_object_attribute)
.service(routes::delete_object)
.service(routes::get_address)
.service(routes::get_all_attributes)
.service(routes::api_refresh)
.service(routes::list_hier)
.service(routes::list_hier_roots)
.service(routes::store_info)
.service(routes::get_jobs)
.service(routes::get_info);
let ui_path = get_static_dir("webui");
if ui_path.is_err() {
warn!(
"Couldn't locate Web UI directory ({:?}), disabling...",
ui_path
);
}
if ui_enabled {
if let Some(ui_path) = &ui_path {
return app
.service(actix_files::Files::new("/", ui_path).index_file("index.html"));
let ui_enabled = ui_path.is_ok() && !args.no_ui;
let browser_enabled = !args.no_desktop && ui_enabled && !args.no_browser;
let preview_path = upend.path.join("previews");
#[cfg(feature = "previews")]
let preview_store = Some(Arc::new(crate::previews::PreviewStore::new(
preview_path.clone(),
store.clone(),
)));
#[cfg(feature = "previews")]
let preview_pool = Some(Arc::new(
rayon::ThreadPoolBuilder::new()
.num_threads(num_cpus::get() / 2)
.build()
.unwrap(),
));
if args.clean {
info!("Cleaning temporary directories...");
if preview_path.exists() {
std::fs::remove_dir_all(&preview_path).unwrap();
debug!("Removed {preview_path:?}");
} else {
debug!("No preview path exists, continuing...");
}
}
app
});
#[cfg(not(feature = "previews"))]
let preview_store = None;
#[cfg(not(feature = "previews"))]
let preview_pool = None;
let bind_result = server.bind(&bind);
if let Ok(server) = bind_result {
break server;
} else {
warn!("Failed to bind at {:?}, trying next port number...", bind);
bind.set_port(bind.port() + 1);
}
let mut bind: SocketAddr = args.bind.parse().expect("Incorrect bind format.");
if cnt > 32 {
panic!("Couldn't start server.")
} else {
cnt += 1;
}
};
let secret = args.secret.unwrap_or_else(|| {
warn!("No secret supplied, generating one at random.");
info!("Starting server at: {}", &bind);
server.run();
thread_rng()
.sample_iter(&rand::distributions::Alphanumeric)
.take(32)
.map(char::from)
.collect()
});
if !args.no_initial_update {
info!("Running initial update...");
let initial = open_result.new;
block_background::<_, _, anyhow::Error>(move || {
let _ = state.store.update(&upend, job_container.clone(), initial);
let _ = extractors::extract_all(upend, state.store, job_container);
Ok(())
})
}
let state = routes::State {
upend: upend.clone(),
store,
job_container: job_container.clone(),
preview_store,
preview_pool,
config: UpEndConfig {
vault_name: Some(args.vault_name.unwrap_or_else(|| {
vault_path
.iter()
.last()
.unwrap()
.to_string_lossy()
.into_owned()
})),
desktop_enabled: !args.no_desktop,
trust_executables: args.trust_executables,
key: args.key,
secret,
},
};
#[cfg(feature = "desktop")]
{
if browser_enabled && ui_enabled {
let ui_result = webbrowser::open(&format!("http://localhost:{}", bind.port()));
if ui_result.is_err() {
warn!("Could not open UI in browser!");
// Start HTTP server
let mut cnt = 0;
let ui_path = ui_path.ok();
let server = loop {
let state = state.clone();
let ui_path = ui_path.clone();
let allowed_origins = args.allow_host.clone();
let server = HttpServer::new(move || {
let allowed_origins = allowed_origins.clone();
let cors = Cors::default()
.allowed_origin("http://localhost")
.allowed_origin_fn(|origin, _req_head| {
origin.as_bytes().starts_with(b"http://localhost:")
})
.allowed_origin_fn(move |origin, _req_head| {
allowed_origins.iter().any(|allowed_origin| {
*allowed_origin == "*" || origin == allowed_origin
})
})
.allow_any_method();
let app = App::new()
.wrap(cors)
.app_data(actix_web::web::PayloadConfig::new(4_294_967_296))
.data(state.clone())
.wrap(middleware::Logger::default().exclude("/api/jobs"))
.service(routes::login)
.service(routes::get_raw)
.service(routes::get_thumbnail)
.service(routes::get_query)
.service(routes::get_object)
.service(routes::put_object)
.service(routes::put_blob)
.service(routes::put_object_attribute)
.service(routes::delete_object)
.service(routes::get_address)
.service(routes::get_all_attributes)
.service(routes::api_refresh)
.service(routes::list_hier)
.service(routes::list_hier_roots)
.service(routes::store_info)
.service(routes::get_jobs)
.service(routes::get_info);
if ui_enabled {
if let Some(ui_path) = &ui_path {
return app.service(
actix_files::Files::new("/", ui_path).index_file("index.html"),
);
}
}
app
});
let bind_result = server.bind(&bind);
if let Ok(server) = bind_result {
break server;
} else {
warn!("Failed to bind at {:?}, trying next port number...", bind);
bind.set_port(bind.port() + 1);
}
if cnt > 32 {
panic!("Couldn't start server.")
} else {
cnt += 1;
}
};
info!("Starting server at: {}", &bind);
server.run();
if !args.no_initial_update {
info!("Running initial update...");
let initial = open_result.new;
block_background::<_, _, anyhow::Error>(move || {
let _ = state.store.update(&upend, job_container.clone(), initial);
let _ = extractors::extract_all(upend, state.store, job_container);
Ok(())
})
}
#[cfg(feature = "desktop")]
{
if browser_enabled && ui_enabled {
let ui_result = webbrowser::open(&format!("http://localhost:{}", bind.port()));
if ui_result.is_err() {
warn!("Could not open UI in browser!");
}
}
}
Ok(sys.run()?)
}
}
Ok(sys.run()?)
}

View File

@ -1 +0,0 @@
/target

File diff suppressed because it is too large Load Diff

View File

@ -1,28 +0,0 @@
[package]
name = "upend_cli"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
clap = { version = "4.0.32", features = ["derive", "color"] }
reqwest = { version = "0.11.13", features = ["blocking", "json"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
multibase = "0.9"
multihash = { version = "*", default-features = false, features = [
"alloc",
"multihash-impl",
"sha2",
"identity",
] }
uuid = { version = "0.8", features = ["v4"] }
filebuffer = "0.4.0"
log = "0.4"
anyhow = "1.0.68"
env_logger = "0.10.0"

View File

@ -1,114 +0,0 @@
use anyhow::{anyhow, Result};
use multihash::{Code, Hasher, Multihash, MultihashDigest};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Entry {
pub entity: String,
pub attribute: String,
pub value: EntryValue,
}
#[derive(Debug, Clone)]
pub struct ImmutableEntry(pub Entry);
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InvariantEntry {
pub attribute: String,
pub value: EntryValue,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "t", content = "c")]
pub enum EntryValue {
String(String),
Number(f64),
Address(String),
Null,
Invalid,
}
impl std::str::FromStr for EntryValue {
type Err = std::convert::Infallible;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.len() < 2 {
match s.chars().next() {
Some('S') => Ok(EntryValue::String("".into())),
Some('X') => Ok(EntryValue::Null),
_ => Ok(EntryValue::Invalid),
}
} else {
let (type_char, content) = s.split_at(1);
match (type_char, content) {
("S", content) => Ok(EntryValue::String(String::from(content))),
("N", content) => {
if let Ok(n) = content.parse::<f64>() {
Ok(EntryValue::Number(n))
} else {
Ok(EntryValue::Invalid)
}
}
("O", content) => {
Ok(EntryValue::Address(String::from(content)))
}
_ => Ok(EntryValue::Invalid),
}
}
}
}
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct Hash(pub Vec<u8>);
impl AsRef<[u8]> for Hash {
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
}
}
pub fn hash<T: AsRef<[u8]>>(input: T) -> Hash {
let mut hasher = multihash::Sha2_256::default();
hasher.update(input.as_ref());
Hash(Vec::from(hasher.finalize()))
}
fn b58_encode<T: AsRef<[u8]>>(vec: T) -> String {
multibase::encode(multibase::Base::Base58Btc, vec.as_ref())
}
#[derive(Clone, Eq, PartialEq, Hash)]
pub enum Address {
Hash(Hash),
Attribute(String),
Url(String),
}
// multihash SHA2-256
const SHA2_256: u64 = 0x12;
// multihash identity
const IDENTITY: u64 = 0x00;
impl Address {
pub fn encode(&self) -> Result<Vec<u8>> {
let hash = match self {
Self::Hash(hash) => Multihash::wrap(SHA2_256, &hash.0).map_err(|err| anyhow!(err))?,
Self::Attribute(attribute) => {
Code::Identity.digest(&[&[b'A'], attribute.as_bytes()].concat())
}
Self::Url(url) => Code::Identity.digest(&[&[b'X'], url.as_bytes()].concat()),
};
Ok(hash.to_bytes())
}
}
impl std::fmt::Display for Address {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
b58_encode(self.encode().map_err(|_| std::fmt::Error)?)
)
}
}

View File

@ -1,155 +0,0 @@
use anyhow::anyhow;
use clap::{Parser, Subcommand, ValueEnum};
use filebuffer::FileBuffer;
use log::{debug, error, trace};
use reqwest::Url;
use serde_json::json;
use std::path::PathBuf;
use crate::common::EntryValue;
mod common;
/// Command-line client for UpEnd
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None, arg_required_else_help=true)]
struct Args {
#[command(subcommand)]
command: Option<Commands>,
/// Output format
#[arg(short, long)]
format: Option<OutputFormat>,
}
#[derive(Subcommand, Debug)]
enum Commands {
Query {
/// URL of the UpEnd instance to query.
#[arg(short, long)]
url: Option<Url>,
/// The query itself, in L-expression format.
query: String,
},
/// Insert an entry.
Insert {
/// URL of the UpEnd instance to query.
#[arg(short, long)]
url: Option<Url>,
/// The address of the entity.
entity: String,
// The attribute.
attribute: String,
// The value.
value: common::EntryValue,
},
/// Get the address of a file, attribute or URL.
Address {
/// Type of input to be addressed
_type: AddressType,
/// Path to a file, hash...
input: String,
},
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, ValueEnum)]
enum OutputFormat {
Json,
Tsv,
Raw,
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, ValueEnum)]
enum AddressType {
/// Hash a file and output its address.
File,
/// Compute an address from the output of `sha256sum`
Sha256sum,
}
fn main() -> anyhow::Result<()> {
env_logger::init();
let args = Args::parse();
let format = args.format.unwrap_or(OutputFormat::Tsv);
match args.command {
Some(Commands::Query { url, query }) => {
let url = url.unwrap_or("http://localhost:8093".parse().unwrap());
let api_url = url.join("/api/query")?;
debug!("Querying \"{}\"", api_url);
let client = reqwest::blocking::Client::new();
let response = client.post(api_url).body(query).send()?;
match response.error_for_status_ref() {
Ok(_) => match format {
OutputFormat::Json | OutputFormat::Raw => println!("{}", response.text()?),
OutputFormat::Tsv => todo!(),
},
Err(err) => {
error!("{}", response.text()?);
return Err(err.into());
}
}
}
Some(Commands::Insert {
url,
entity,
attribute,
value,
}) => {
let url = url.unwrap_or("http://localhost:8093".parse().unwrap());
let api_url = url.join("/api/obj")?;
if let EntryValue::Invalid = value {
return Err(anyhow!("Invalid entry value."));
}
let body = json!({
"entity": entity,
"attribute": attribute,
"value": value
});
debug!("Inserting {:?} at \"{}\"", body, api_url);
let client = reqwest::blocking::Client::new();
let response = client.put(api_url).json(&body).send()?;
match response.error_for_status_ref() {
Ok(_) => {
let data: Vec<String> = response.json()?;
println!("{}", data[0]);
}
Err(err) => {
error!("{}", response.text()?);
return Err(err.into());
}
}
}
Some(Commands::Address { _type, input }) => {
let address = match _type {
AddressType::File => {
let filepath = PathBuf::from(input);
debug!("Hashing {:?}...", filepath);
let fbuffer = FileBuffer::open(&filepath)?;
let digest = common::hash(&fbuffer);
trace!("Finished hashing {:?}...", &filepath);
common::Address::Hash(digest)
}
AddressType::Sha256sum => {
let digest = multibase::Base::Base16Lower.decode(input)?;
common::Address::Hash(common::Hash(digest))
}
};
match format {
OutputFormat::Json => println!("\"{}\"", address),
OutputFormat::Tsv | OutputFormat::Raw => println!("{}", address),
}
}
None => {}
}
Ok(())
}