use std::env;
use std::fs;
use std::path::{Path, PathBuf};
pub use xidlc::error::IdlcError;
#[derive(Clone, Debug)]
pub struct Builder {
lang: String,
out_dir: Option<PathBuf>,
output_filename: Option<PathBuf>,
client: bool,
server: bool,
mock: bool,
}
impl Default for Builder {
fn default() -> Self {
Self {
lang: "rust".to_string(),
out_dir: None,
output_filename: None,
client: true,
server: true,
mock: false,
}
}
}
impl Builder {
pub fn new() -> Self {
Self::default()
}
pub fn with_lang(mut self, lang: impl Into<String>) -> Self {
self.lang = lang.into();
self
}
pub fn with_out_dir(mut self, out_dir: impl Into<PathBuf>) -> Self {
self.out_dir = Some(out_dir.into());
self
}
pub fn with_mock(mut self, mock: bool) -> Self {
self.mock = mock;
self
}
pub fn with_output_filename(mut self, filename: impl Into<PathBuf>) -> Self {
self.output_filename = Some(filename.into());
self
}
pub fn with_client(mut self, value: bool) -> Self {
self.client = value;
self
}
pub fn with_server(mut self, value: bool) -> Self {
self.server = value;
self
}
}
impl Builder {
pub fn compile(&self, inputs: &[impl AsRef<Path>]) -> Result<(), IdlcError> {
let out_dir = match &self.out_dir {
Some(path) => path.clone(),
None => PathBuf::from(
env::var("OUT_DIR")
.map_err(|err| IdlcError::fmt(format!("OUT_DIR is not set: {err}")))?,
),
};
let inputs_paths: Vec<PathBuf> = inputs.iter().map(|p| p.as_ref().to_path_buf()).collect();
let args = xidlc::driver::ArgsGenerate {
lang: self.lang.clone(),
out_dir: out_dir.to_string_lossy().to_string(),
files: inputs_paths.clone(),
client: self.client,
server: self.server,
mock: self.mock,
dry_run: false,
};
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap()
.block_on(async { xidlc::driver::Driver::run(args).await })?;
if let Some(custom_name) = &self.output_filename {
self.apply_output_filename(&out_dir, custom_name, &inputs_paths)?;
}
Ok(())
}
fn apply_output_filename(
&self,
out_dir: &Path,
custom_name: &Path,
inputs: &[PathBuf],
) -> Result<(), IdlcError> {
if out_dir == Path::new("-") {
return Err(IdlcError::fmt(
"with_output_filename is not supported when out_dir is '-'",
));
}
let default_name = match self.lang.as_str() {
"openapi" => {
let stem = inputs
.first()
.and_then(|p| p.file_stem())
.and_then(|s| s.to_str())
.unwrap_or("openapi");
format!("openapi_{}.json", stem)
}
"openrpc" | "open-rpc" => "openrpc.json".to_string(),
_ => {
return Err(IdlcError::fmt(format!(
"with_output_filename is only supported for openapi/openrpc generators, got '{}'",
self.lang
)));
}
};
let src = out_dir.join(&default_name);
if !src.exists() {
return Err(IdlcError::fmt(format!(
"generated file '{}' does not exist in '{}'",
default_name,
out_dir.display()
)));
}
let dst = if custom_name.is_absolute() {
custom_name.to_path_buf()
} else {
out_dir.join(custom_name)
};
if src == dst {
return Ok(());
}
if let Some(parent) = dst.parent() {
fs::create_dir_all(parent)?;
}
fs::rename(src, dst)?;
Ok(())
}
}