mod cli;
mod docgen;
mod formatter;
use clap::Parser;
use cli::{Cli, Commands, Source};
use formatter::{FormattedString, GenericError};
use futures_util::StreamExt;
use granc_core::client::{Descriptor, DynamicRequest, DynamicResponse, GrancClient};
use std::process;
#[tokio::main]
async fn main() {
let args = Cli::parse();
match args.command {
Commands::Call {
endpoint,
uri,
body,
headers,
file_descriptor_set,
} => {
let response = call(endpoint, uri, body, headers, file_descriptor_set).await;
let formatted = match response {
DynamicResponse::Unary(Ok(value)) => FormattedString::from(value),
DynamicResponse::Unary(Err(status)) => FormattedString::from(status),
DynamicResponse::Streaming(stream) => {
let elems = stream.collect::<Vec<_>>().await;
FormattedString::from(elems)
}
};
println!("{formatted}")
}
Commands::List { source } => {
let services = list(source.value()).await;
println!(
"{}",
FormattedString::from(formatter::ServiceList(services))
)
}
Commands::Describe { symbol, source } => {
let descriptor = describe(symbol, source.value()).await;
println!("{}", FormattedString::from(descriptor))
}
Commands::Doc {
symbol,
source,
output,
} => {
let descriptor = describe(symbol.clone(), source.value()).await;
let service_descriptor = descriptor
.service_descriptor()
.cloned()
.ok_or(GenericError("The symbol must be a Service", symbol))
.unwrap_or_exit();
docgen::markdown::generate(output, service_descriptor)
.map_err(|e| GenericError("Failed to generate docs", e))
.unwrap_or_exit();
println!("Documentation generated successfully.");
}
}
}
async fn call(
endpoint: (String, String),
uri: String,
body: serde_json::Value,
headers: Vec<(String, String)>,
file_descriptor_set: Option<std::path::PathBuf>,
) -> DynamicResponse {
let (service, method) = endpoint;
let request = DynamicRequest {
service,
method,
body,
headers,
};
let mut client = GrancClient::connect(&uri).await.unwrap_or_exit();
if let Some(path) = file_descriptor_set {
let bytes = std::fs::read(path).unwrap_or_exit();
let mut client = client.with_file_descriptor(bytes).unwrap_or_exit();
client.dynamic(request).await.unwrap_or_exit()
} else {
client.dynamic(request).await.unwrap_or_exit()
}
}
async fn list(source: Source) -> Vec<String> {
match source {
Source::Uri(uri) => {
let mut client = GrancClient::connect(&uri).await.unwrap_or_exit();
client
.list_services()
.await
.map_err(|e| GenericError("Failed to list services:", e))
.unwrap_or_exit()
}
Source::File(path) => {
let fd_bytes = std::fs::read(path).unwrap_or_exit();
let client = GrancClient::offline(fd_bytes).unwrap_or_exit();
client.list_services()
}
}
}
async fn describe(symbol: String, source: Source) -> Descriptor {
match source {
Source::Uri(uri) => {
let mut client = GrancClient::connect(&uri).await.unwrap_or_exit();
client
.get_descriptor_by_symbol(&symbol)
.await
.unwrap_or_exit()
}
Source::File(path) => {
let fd_bytes = std::fs::read(path).unwrap_or_exit();
let client = GrancClient::offline(fd_bytes).unwrap_or_exit();
client
.get_descriptor_by_symbol(&symbol)
.ok_or(GenericError("Symbol not found", symbol))
.unwrap_or_exit()
}
}
}
trait UnwrapOrExit<T, E> {
fn unwrap_or_exit(self) -> T;
}
impl<T, E> UnwrapOrExit<T, E> for Result<T, E>
where
E: Into<FormattedString>,
{
fn unwrap_or_exit(self) -> T {
match self {
Ok(v) => v,
Err(e) => {
eprintln!("{}", Into::<FormattedString>::into(e));
process::exit(1);
}
}
}
}