trident_client/
idl_loader.rs

1use heck::ToSnakeCase;
2use std::fmt;
3use std::fs::File;
4use std::fs::{self};
5use std::io::Read;
6use std::path::PathBuf;
7
8use trident_idl_spec::Idl;
9
10/// Custom error type for IDL loading operations
11#[derive(Debug)]
12pub enum IdlError {
13    IoError {
14        source: std::io::Error,
15        path: PathBuf,
16        operation: &'static str,
17    },
18    ParseError {
19        source: serde_json::Error,
20        path: PathBuf,
21    },
22    NoIdlsFound {
23        path: String,
24    },
25}
26
27impl fmt::Display for IdlError {
28    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29        match self {
30            IdlError::IoError {
31                source,
32                path,
33                operation,
34            } => {
35                write!(
36                    f,
37                    "Failed to {} '{}': {}",
38                    operation,
39                    path.display(),
40                    source
41                )
42            }
43            IdlError::ParseError { source, path } => {
44                write!(
45                    f,
46                    "Failed to parse IDL file '{}': {}",
47                    path.display(),
48                    source
49                )
50            }
51            IdlError::NoIdlsFound { path } => {
52                write!(f, "No IDL files found in {}", path)
53            }
54        }
55    }
56}
57
58impl std::error::Error for IdlError {
59    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
60        match self {
61            IdlError::IoError { source, .. } => Some(source),
62            IdlError::ParseError { source, .. } => Some(source),
63            IdlError::NoIdlsFound { .. } => None,
64        }
65    }
66}
67
68/// Loads IDL files from a directory
69///
70/// # Arguments
71///
72/// * `dir_path` - Path to the directory containing IDL files
73/// * `program_name` - Optional program name to filter IDL files
74///
75/// # Returns
76///
77/// A Result containing a vector of parsed IDL files or an error with context
78pub fn load_idls(dir_path: PathBuf, program_name: Option<String>) -> Result<Vec<Idl>, IdlError> {
79    let mut idls = Vec::new();
80
81    // Read the directory and iterate over each entry
82    let read_dir = fs::read_dir(&dir_path).map_err(|e| IdlError::IoError {
83        source: e,
84        path: dir_path.clone(),
85        operation: "read directory",
86    })?;
87
88    for entry_result in read_dir {
89        let entry = entry_result.map_err(|e| IdlError::IoError {
90            source: e,
91            path: dir_path.clone(),
92            operation: "read directory entry",
93        })?;
94
95        let path = entry.path();
96
97        if let Some(ref program_name) = program_name {
98            if path.is_file()
99                && !path
100                    .file_name()
101                    .and_then(|name| name.to_str())
102                    // convert program_name to match case of IDL names
103                    .map(|name| name.trim_end_matches(".json") == program_name.to_snake_case())
104                    .unwrap_or(false)
105            {
106                continue;
107            }
108        }
109
110        // Only process .json files
111        if path.is_file() && path.extension().and_then(|ext| ext.to_str()) == Some("json") {
112            // Open the file in read-only mode
113            let mut file = File::open(&path).map_err(|e| IdlError::IoError {
114                source: e,
115                path: path.clone(),
116                operation: "open file",
117            })?;
118
119            // Read the file contents into a string
120            let mut json_content = String::new();
121            file.read_to_string(&mut json_content)
122                .map_err(|e| IdlError::IoError {
123                    source: e,
124                    path: path.clone(),
125                    operation: "read file",
126                })?;
127
128            // Parse the string of data into an Idl struct
129            match serde_json::from_str::<Idl>(&json_content) {
130                Ok(parsed_idl) => {
131                    idls.push(parsed_idl);
132                }
133                Err(e) => {
134                    // Instead of just printing the error, collect it in our custom error type
135                    return Err(IdlError::ParseError {
136                        source: e,
137                        path: path.clone(),
138                    });
139                }
140            }
141        }
142    }
143
144    if idls.is_empty() {
145        return Err(IdlError::NoIdlsFound {
146            path: dir_path.to_string_lossy().to_string(),
147        });
148    }
149
150    Ok(idls)
151}