mod codegen;
mod entity_data_model_parse;
mod entity_model;
mod entity_model_filter;
#[cfg(test)]
mod input_file_test;
use std::{
ffi::OsStr,
fs::{self, DirBuilder},
io::Write,
path::Path,
process::{Command, Stdio},
};
use anyhow::{anyhow, Context};
use bumpalo::Bump;
use codegen::generate_client_module;
use proc_macro2::TokenStream;
use which::which;
use entity_model::construct::construct_entity_model;
pub use entity_model::construct::ConstructConfig;
pub use entity_model_filter::item_name_whitelist::ItemNameWhitelist;
pub use entity_model_filter::EntityModelFilter;
pub fn generate_module(
metadata_xml: &str,
service_url: String,
construct_config: ConstructConfig,
entity_model_filter: Option<&mut dyn EntityModelFilter>,
) -> Result<TokenStream, anyhow::Error> {
const TRIMMED_XML_LEN: usize = 100;
let trimmed_input_str = || {
format!(
"{}{}",
&metadata_xml[0..TRIMMED_XML_LEN].replace("\n", ""),
if metadata_xml.len() > TRIMMED_XML_LEN {
"..."
} else {
""
}
)
};
let t_edmx = serde_xml_rs::from_str(metadata_xml).with_context(|| {
format!(
"Unable to parse XML input as entity model document:\n{}",
trimmed_input_str()
)
})?;
let arena = Bump::new();
let entity_model = construct_entity_model(t_edmx, service_url, &arena, construct_config)
.with_context(|| {
format!(
"Unable to construct consistent entity model from document:\n{}",
trimmed_input_str()
)
})?;
Ok(generate_client_module(
&entity_model,
&arena,
entity_model_filter,
))
}
pub fn write_module_build_artifact(
metadata_xml_path: &str,
target_module_path: &str,
service_url: &str,
apply_rustfmt: bool,
construct_config: ConstructConfig,
entity_model_filter: Option<&mut dyn EntityModelFilter>,
) -> Result<(), anyhow::Error> {
let target_module_path = Path::new(target_module_path);
if target_module_path.extension() != Some(OsStr::new("rs")) {
return Err(anyhow!(
"Specified target module code file path '{:?}' does not end with '.rs'",
target_module_path
));
}
let metadata_xml = fs::read_to_string(metadata_xml_path)
.with_context(|| "Could not read metadata XML file")?;
let module_content_stream = generate_module(
&metadata_xml,
service_url.to_owned(),
construct_config,
entity_model_filter,
)?;
let module_content_buf = if apply_rustfmt {
run_rustfmt(&module_content_stream.to_string())?
} else {
module_content_stream.to_string()
};
let module_parent_dir = target_module_path.parent().unwrap();
DirBuilder::new()
.recursive(true)
.create(module_parent_dir)?;
fs::write(target_module_path, module_content_buf)?;
println!("cargo:rerun-if-changed={}", metadata_xml_path);
Ok(())
}
pub(crate) fn run_rustfmt(input: &str) -> Result<String, anyhow::Error> {
let rustfmt_path = which("rustfmt")
.with_context(|| "Could not run `rustfmt`; ensure it is installed on the system")?;
let mut fmt_proc = Command::new(rustfmt_path)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.with_context(|| "Failed to spawn `rustfmt` process")?;
{
let mut stdin = fmt_proc.stdin.take().unwrap();
stdin.write(input.as_bytes())?;
}
let rustfmt_output = fmt_proc.wait_with_output()?;
if rustfmt_output.status.success() {
let formatted = String::from_utf8(rustfmt_output.stdout).unwrap();
Ok(formatted)
} else {
let rustfmt_err_out = std::str::from_utf8(&rustfmt_output.stderr).unwrap();
Err(anyhow!(
"Syntax error in token stream:\n{}",
rustfmt_err_out
))
}
}