use anyhow::{anyhow, Result};
use async_std::{io, path::PathBuf, prelude::*};
use async_trait::async_trait;
use codec::{Decode, Encode};
use std::{convert::Infallible, str::FromStr};
use structopt::StructOpt;
use sube::{http, ws, Backend, Metadata, StorageKey, Sube};
use surf::Url;
#[derive(StructOpt, Debug)]
#[structopt(name = "sube")]
struct Opt {
#[structopt(short, long)]
pub chain: String,
#[structopt(short, long, default_value = "json")]
pub output: Output,
#[structopt(short, long)]
pub metadata: Option<PathBuf>,
#[structopt(short, long)]
pub quiet: bool,
#[structopt(short, long, parse(from_occurrences))]
pub verbose: usize,
#[structopt(subcommand)]
pub cmd: Cmd,
}
#[derive(StructOpt, Debug)]
enum Cmd {
Meta,
#[structopt(visible_alias = "q")]
Query { query: String },
#[structopt(visible_alias = "s")]
Submit,
#[structopt(visible_alias = "e")]
Encode,
#[structopt(visible_alias = "d")]
Decode,
}
#[async_std::main]
async fn main() {
match run().await {
Ok(_) => {}
Err(err) => {
log::error!("{}", err);
std::process::exit(1);
}
}
}
async fn run() -> Result<()> {
let opt = Opt::from_args();
stderrlog::new()
.verbosity(opt.verbose)
.quiet(opt.quiet)
.init()
.unwrap();
let url = chain_string_to_url(opt.chain)?;
log::debug!("Matching backend for {}", url);
let backend: AnyBackend = match url.scheme() {
"http" | "https" => AnyBackend::Http(http::Backend::new(url)),
"ws" | "wss" => AnyBackend::Ws(ws::Backend::new_ws2(url.as_ref()).await?),
_ => return Err(anyhow!("Not supported")),
};
let client: Sube<_> = if opt.metadata.is_none() {
backend.into()
} else {
let meta_path = &opt.metadata.unwrap();
let mut m = Vec::new();
let mut f = async_std::fs::File::open(meta_path).await?;
f.read_to_end(&mut m).await?;
let meta = Metadata::decode(&mut m.as_slice())?;
Sube::new_with_meta(backend, meta)
};
let meta = client.metadata().await?;
let out: Vec<_> = match opt.cmd {
Cmd::Query { query } => {
let value = client.query(&query).await?;
match opt.output {
Output::Scale => value.as_ref().into(),
Output::Json => value.to_string().as_bytes().into(),
Output::Hex => format!("0x{}", hex::encode(value.as_ref()))
.as_bytes()
.into(),
}
}
Cmd::Submit => {
let mut input = String::new();
io::stdin().read_line(&mut input).await?;
client.submit(input).await?;
vec![]
}
Cmd::Meta => match opt.output {
Output::Scale => meta.encode(),
Output::Json => serde_json::to_string(&meta)?.into(),
Output::Hex => format!("0x{}", hex::encode(meta.encode())).into(),
},
Cmd::Encode => {
todo!()
}
Cmd::Decode => {
todo!()
}
};
io::stdout().write_all(&out).await?;
writeln!(io::stdout()).await?;
Ok(())
}
#[derive(Debug)]
enum Output {
Json,
Scale,
Hex,
}
impl FromStr for Output {
type Err = Infallible;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(match s {
"json" => Output::Json,
"scale" => Output::Scale,
"hex" => Output::Hex,
_ => Output::Json,
})
}
}
fn chain_string_to_url(mut chain: String) -> Result<Url> {
if !chain.starts_with("ws://")
&& !chain.starts_with("wss://")
&& !chain.starts_with("http://")
&& !chain.starts_with("https://")
{
chain = ["http", &chain].join("://");
}
let mut url = Url::parse(&chain)?;
if url.host_str().eq(&Some("localhost")) && url.port().is_none() {
const WS_PORT: u16 = 9944;
const HTTP_PORT: u16 = 9933;
let port = match url.scheme() {
"ws" => WS_PORT,
_ => HTTP_PORT,
};
url.set_port(Some(port)).expect("known port");
}
Ok(url)
}
enum AnyBackend {
Ws(ws::Backend<ws::WS2>),
Http(http::Backend),
}
#[async_trait]
impl Backend for AnyBackend {
async fn query_bytes(&self, key: &StorageKey) -> sube::Result<Vec<u8>> {
match self {
AnyBackend::Ws(b) => b.query_bytes(key).await,
AnyBackend::Http(b) => b.query_bytes(key).await,
}
}
async fn submit<T>(&self, ext: T) -> sube::Result<()>
where
T: AsRef<[u8]> + Send,
{
match self {
AnyBackend::Ws(b) => b.submit(ext).await,
AnyBackend::Http(b) => b.submit(ext).await,
}
}
async fn metadata(&self) -> sube::Result<sube::Metadata> {
match self {
AnyBackend::Ws(b) => b.metadata().await,
AnyBackend::Http(b) => b.metadata().await,
}
}
}