use heck::ToSnakeCase;
use std::fmt;
use std::fs::File;
use std::fs::{self};
use std::io::Read;
use std::path::PathBuf;
use trident_idl_spec::Idl;
#[derive(Debug)]
pub enum IdlError {
IoError {
source: std::io::Error,
path: PathBuf,
operation: &'static str,
},
ParseError {
source: serde_json::Error,
path: PathBuf,
},
NoIdlsFound {
path: String,
},
}
impl fmt::Display for IdlError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
IdlError::IoError {
source,
path,
operation,
} => {
write!(
f,
"Failed to {} '{}': {}",
operation,
path.display(),
source
)
}
IdlError::ParseError { source, path } => {
write!(
f,
"Failed to parse IDL file '{}': {}",
path.display(),
source
)
}
IdlError::NoIdlsFound { path } => {
write!(f, "No IDL files found in {}", path)
}
}
}
}
impl std::error::Error for IdlError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
IdlError::IoError { source, .. } => Some(source),
IdlError::ParseError { source, .. } => Some(source),
IdlError::NoIdlsFound { .. } => None,
}
}
}
pub fn load_idls_from_files(
file_paths: Vec<PathBuf>,
program_name: Option<String>,
) -> Result<Vec<Idl>, IdlError> {
let mut idls = Vec::new();
for path in file_paths {
if !path.exists() {
return Err(IdlError::IoError {
source: std::io::Error::new(std::io::ErrorKind::NotFound, "File does not exist"),
path: path.clone(),
operation: "check file existence",
});
}
if !path.is_file() {
return Err(IdlError::IoError {
source: std::io::Error::new(std::io::ErrorKind::InvalidInput, "Path is not a file"),
path: path.clone(),
operation: "validate file path",
});
}
if path.extension().and_then(|ext| ext.to_str()) != Some("json") {
return Err(IdlError::IoError {
source: std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"File is not a JSON file",
),
path: path.clone(),
operation: "validate file extension",
});
}
if let Some(ref program_name) = program_name {
if !path
.file_name()
.and_then(|name| name.to_str())
.map(|name| name.trim_end_matches(".json") == program_name.to_snake_case())
.unwrap_or(false)
{
continue;
}
}
let mut file = File::open(&path).map_err(|e| IdlError::IoError {
source: e,
path: path.clone(),
operation: "open file",
})?;
let mut json_content = String::new();
file.read_to_string(&mut json_content)
.map_err(|e| IdlError::IoError {
source: e,
path: path.clone(),
operation: "read file",
})?;
match serde_json::from_str::<Idl>(&json_content) {
Ok(mut parsed_idl) => {
if parsed_idl.metadata.name.is_empty() {
if let Some(file_stem) = path.file_stem().and_then(|s| s.to_str()) {
parsed_idl.metadata.name = file_stem.to_string();
}
}
idls.push(parsed_idl);
}
Err(e) => {
return Err(IdlError::ParseError {
source: e,
path: path.clone(),
});
}
}
}
if idls.is_empty() {
return Err(IdlError::NoIdlsFound {
path: "specified file paths".to_string(),
});
}
Ok(idls)
}
pub fn load_idls(dir_path: PathBuf, program_name: Option<String>) -> Result<Vec<Idl>, IdlError> {
let mut idls = Vec::new();
let read_dir = fs::read_dir(&dir_path).map_err(|e| IdlError::IoError {
source: e,
path: dir_path.clone(),
operation: "read directory",
})?;
for entry_result in read_dir {
let entry = entry_result.map_err(|e| IdlError::IoError {
source: e,
path: dir_path.clone(),
operation: "read directory entry",
})?;
let path = entry.path();
if let Some(ref program_name) = program_name {
if path.is_file()
&& !path
.file_name()
.and_then(|name| name.to_str())
.map(|name| name.trim_end_matches(".json") == program_name.to_snake_case())
.unwrap_or(false)
{
continue;
}
}
if path.is_file() && path.extension().and_then(|ext| ext.to_str()) == Some("json") {
let mut file = File::open(&path).map_err(|e| IdlError::IoError {
source: e,
path: path.clone(),
operation: "open file",
})?;
let mut json_content = String::new();
file.read_to_string(&mut json_content)
.map_err(|e| IdlError::IoError {
source: e,
path: path.clone(),
operation: "read file",
})?;
match serde_json::from_str::<Idl>(&json_content) {
Ok(mut parsed_idl) => {
if parsed_idl.metadata.name.is_empty() {
if let Some(file_stem) = path.file_stem().and_then(|s| s.to_str()) {
parsed_idl.metadata.name = file_stem.to_string();
}
}
idls.push(parsed_idl);
}
Err(e) => {
return Err(IdlError::ParseError {
source: e,
path: path.clone(),
});
}
}
}
}
if idls.is_empty() {
return Err(IdlError::NoIdlsFound {
path: dir_path.to_string_lossy().to_string(),
});
}
Ok(idls)
}