mod cli {
use std::path::Path;
#[cfg(feature = "serde")]
use std::{fs::File, io::BufWriter, time::Instant};
use clap::{Parser, Subcommand};
use thiserror::Error;
#[cfg(feature = "serde")]
use serde::Serialize;
use mavinspect::parser::MAVInspector;
#[cfg(feature = "serde")]
use mavinspect::proto::mavlink_messages_v1 as proto;
#[cfg(feature = "serde")]
use mavinspect::protocol;
const DEFAULT_HOST: &str = "[::1]";
const DEFAULT_PORT: u16 = 50051;
pub const DEFAULT_SRC_PATH: &str = ".";
pub const DEFAULT_OUT_PATH: &str = ".";
#[derive(Error, Debug)]
pub enum CliError {
#[error("this functionality requires `server` feature")]
ServerFeatureRequired,
#[cfg(not(feature = "serde"))]
#[error("this functionality requires `serde` feature")]
SerdeFeatureRequired,
}
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
pub struct Cli {
#[command(subcommand)]
pub command: Commands,
#[arg(long, default_value = DEFAULT_SRC_PATH)]
pub src: String,
}
#[derive(Debug, Subcommand)]
pub enum Commands {
Ls {},
Parse {
#[arg(long, short = 'O', default_value = DEFAULT_OUT_PATH)]
out: String,
#[arg(long, default_value_t = false)]
as_proto: bool,
#[arg(long, default_value_t = false)]
pretty: bool,
},
Serve {
#[arg(long, short = 'H', default_value = DEFAULT_HOST)]
host: String,
#[arg(long, short = 'P', default_value_t = DEFAULT_PORT)]
port: u16,
},
}
pub fn realpath(path: &String) -> String {
Path::new(path)
.canonicalize()
.unwrap()
.to_str()
.unwrap()
.to_string()
}
pub fn list_dialect_definitions(inspector: &MAVInspector) {
if inspector.definitions().is_empty() {
println!("No dialects were parsed.");
return;
}
println!("\r\nDialects:");
for dialect in inspector.definitions() {
println!("\t{}", dialect.name());
println!("\t\tpath: {}", dialect.path());
println!("\t\tversion: {:?}", dialect.version());
println!("\t\tdialect: {:?}", dialect.dialect());
println!(
"\t\tincludes: {:?}",
dialect
.includes()
.iter()
.map(|i| { i.name().to_string() })
.collect::<Vec<String>>()
);
}
}
#[cfg(feature = "serde")]
pub fn parse_dialect_definitions(
out: &str,
as_proto: bool,
pretty: bool,
inspector: &MAVInspector,
) {
println!("\r\nParsing MAVLink protocol XML definitions");
let protocol = inspector.parse().unwrap();
println!("\r\nParsed {} dialects.", protocol.dialects().len());
println!("\r\nWriting dialects");
println!("\tpath: {out}");
let started_at = Instant::now();
println!("\r\nDialects:");
for (name, dialect) in protocol.dialects() {
let (writer, display_path) = open_file_writer(out, format!("{}.json", name).as_str());
println!("\t{name}");
println!("\t\tpath: {display_path}");
if pretty {
println!("\t\tUse pretty JSON output.");
}
if as_proto {
let proto_specs = dialect.to_proto();
println!("\t\tas Protobuf: true");
write_data::<proto::Dialect>(writer, &proto_specs, pretty);
} else {
write_data::<protocol::Dialect>(writer, dialect, pretty);
}
}
let ended_at = Instant::now();
let duration = ended_at - started_at;
println!("\n\rWriting completed");
println!("\tduration: {}s", (duration.as_micros() as f64) / 1000000.0);
}
#[cfg(feature = "serde")]
fn open_file_writer(base: &str, file_path: &str) -> (BufWriter<File>, String) {
let path = Path::new(base).join(Path::new(file_path));
let display = path.display();
let file = match File::create(&path) {
Err(why) => panic!("couldn't create file {}: {}", display, why),
Ok(file) => file,
};
(
BufWriter::new(file),
path.canonicalize().unwrap().display().to_string(),
)
}
#[cfg(feature = "serde")]
fn write_data<T: Serialize>(writer: BufWriter<File>, data: &T, pretty: bool) {
if pretty {
serde_json::to_writer_pretty(writer, data).unwrap();
} else {
serde_json::to_writer(writer, data).unwrap();
}
}
pub fn setup() -> (Cli, MAVInspector) {
let cli: Cli = Cli::parse();
println!("[MAVInspect]");
let src_path = realpath(&cli.src);
println!(
"\nLoading XML definitions\n\
\tsource: {}",
&src_path
);
let inspector = MAVInspector::new(&src_path).unwrap();
(cli, inspector)
}
pub fn process(cli: &Cli, inspector: &MAVInspector) -> Result<(), CliError> {
match &cli.command {
Commands::Ls {} => {
list_dialect_definitions(inspector);
}
#[cfg(feature = "serde")]
Commands::Parse {
out,
as_proto,
pretty,
} => {
parse_dialect_definitions(out, *as_proto, *pretty, inspector);
}
#[cfg(not(feature = "serde"))]
Commands::Parse { .. } => {
return Err(CliError::SerdeFeatureRequired);
}
Commands::Serve { .. } => return Err(CliError::ServerFeatureRequired),
}
Ok(())
}
}
#[cfg(feature = "server")]
mod server {
use mavinspect::parser::MAVInspector;
use tonic::transport::Server;
use mavinspect::server::MavInspectServerImplementation;
use mavinspect::server::MavInspectServiceServer;
#[cfg(feature = "reflection")]
mod proto_descriptor {
pub(crate) const FILE_DESCRIPTOR_SET: &[u8] =
tonic::include_file_descriptor_set!("proto_mavinspect_descriptor");
}
pub async fn serve(
host: &str,
port: u16,
inspector: &MAVInspector,
) -> Result<(), Box<dyn std::error::Error>> {
let addr = format!("{host}:{port}").parse().unwrap();
let protocol = inspector.parse().unwrap().to_proto();
let mavinspect = MavInspectServerImplementation::new(protocol);
let router = Server::builder().add_service(MavInspectServiceServer::new(mavinspect));
#[cfg(feature = "reflection")]
let router = {
let reflection_service = tonic_reflection::server::Builder::configure()
.register_encoded_file_descriptor_set(proto_descriptor::FILE_DESCRIPTOR_SET)
.build()
.unwrap();
router.add_service(reflection_service)
};
#[cfg(feature = "health")]
let router = {
let (mut health_reporter, health_service) = tonic_health::server::health_reporter();
health_reporter
.set_serving::<MavInspectServiceServer<MavInspectServerImplementation>>()
.await;
router.add_service(health_service)
};
println!("\n\rMAVInspect gRPC server is listening on {}\n", addr);
router.serve(addr).await?;
Ok(())
}
}
#[cfg(not(feature = "server"))]
fn main() {
use std::process::exit;
use cli::*;
let (cli, inspector) = setup();
match process(&cli, &inspector) {
Ok(_) => {}
Err(CliError::ServerFeatureRequired) => {
eprint!("\n\r[ERROR] Server is not available! Build MAVInspect with 'server' feature.");
exit(-1);
}
#[cfg(not(feature = "serde"))]
Err(CliError::SerdeFeatureRequired) => {
eprint!("\n\r[ERROR] Parser is not available! Build MAVInspect with 'serde' feature.");
exit(-1);
}
}
}
#[cfg(feature = "server")]
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
use cli::*;
let (cli, inspector) = setup();
match process(&cli, &inspector) {
Ok(_) => Ok(()),
Err(_) => match &cli.command {
Commands::Serve { host, port } => {
server::serve(host, *port, &inspector).await?;
Ok(())
}
&_ => panic!("Incorrect command {:?} in server context!", &cli.command),
},
}
}