1#![deny(missing_docs)]
6#![deny(unsafe_code)]
7
8use anyhow::{anyhow, Result};
9use essential_types::{contract::Contract, solution::Solution};
10use std::{
11 fs::DirEntry,
12 path::{Path, PathBuf},
13};
14use tokio::io::{AsyncReadExt, BufReader};
15
16pub async fn read_contract(path: PathBuf) -> Result<Contract> {
20 let bytes = read_bytes(path).await?;
21 let contract = deserialize_contract(bytes).await?;
22 Ok(contract)
23}
24
25pub async fn read_contracts(path: PathBuf) -> Result<Vec<Contract>> {
29 let mut contracts: Vec<Contract> = vec![];
30 for entry in path.read_dir()? {
31 let file_path = dir_entry_to_path(&path, entry?)
32 .inspect_err(|err| println!("skipping file: {}", err))?;
33 check_path_json(&path)?;
34 let bytes = read_bytes(file_path).await?;
35 let contract = deserialize_contract(bytes).await?;
36 contracts.push(contract);
37 }
38 Ok(contracts)
39}
40
41pub async fn read_solution(path: PathBuf) -> Result<Solution> {
45 let bytes = read_bytes(path).await?;
46 let solution = deserialize_solution(bytes).await?;
47 Ok(solution)
48}
49
50pub async fn read_solutions(path: PathBuf) -> Result<Vec<Solution>> {
54 let mut solutions: Vec<Solution> = vec![];
55 for entry in path.read_dir()? {
56 #[cfg(feature = "tracing")]
57 let file_path = dir_entry_to_path(&path, entry?)
58 .inspect_err(|err| tracing::warn!("skipping file: {}", err));
59
60 #[cfg(not(feature = "tracing"))]
61 let file_path = dir_entry_to_path(&path, entry?);
62
63 let Ok(file_path) = file_path else {
64 continue;
65 };
66
67 #[cfg(not(feature = "tracing"))]
68 if check_path_json(&path).is_err() {
69 continue;
70 }
71
72 #[cfg(feature = "tracing")]
73 if let Err(e) = check_path_json(&path) {
74 tracing::warn!("skipping file because it's not json: {}", e);
75 continue;
76 }
77
78 let bytes = read_bytes_inner(file_path).await?;
79 let solution = deserialize_solution(bytes).await?;
80 solutions.push(solution);
81 }
82 Ok(solutions)
83}
84
85pub async fn read_bytes(path: PathBuf) -> Result<Vec<u8>> {
89 check_path_json(&path)?;
90 read_bytes_inner(path).await
91}
92
93async fn read_bytes_inner(path: PathBuf) -> Result<Vec<u8>> {
94 let file = tokio::fs::File::open(path).await?;
95 let mut bytes = Vec::new();
96 let mut reader = BufReader::new(file);
97 reader.read_to_end(&mut bytes).await?;
98 Ok(bytes)
99}
100
101pub async fn read_bytes_dir(path: PathBuf) -> Result<Vec<Vec<u8>>> {
105 let mut all_bytes: Vec<Vec<u8>> = vec![];
106 for entry in path.read_dir()? {
107 #[cfg(feature = "tracing")]
108 let file_path = dir_entry_to_path(&path, entry?)
109 .inspect_err(|err| tracing::warn!("skipping file: {}", err));
110
111 #[cfg(not(feature = "tracing"))]
112 let file_path = dir_entry_to_path(&path, entry?);
113
114 let Ok(file_path) = file_path else {
115 continue;
116 };
117
118 #[cfg(not(feature = "tracing"))]
119 if check_path_json(&path).is_err() {
120 continue;
121 }
122
123 #[cfg(feature = "tracing")]
124 if let Err(e) = check_path_json(&path) {
125 tracing::warn!("skipping file because it's not json: {}", e);
126 continue;
127 }
128
129 let bytes = read_bytes_inner(file_path).await?;
130 all_bytes.push(bytes);
131 }
132 Ok(all_bytes)
133}
134
135pub async fn deserialize_contract(bytes: Vec<u8>) -> Result<Contract> {
137 let contract = serde_json::from_slice::<Contract>(&bytes)?;
138 Ok(contract)
139}
140
141pub async fn deserialize_solution(bytes: Vec<u8>) -> Result<Solution> {
143 let solution = serde_json::from_slice::<Solution>(&bytes)?;
144 Ok(solution)
145}
146
147fn dir_entry_to_path(path: &Path, entry: DirEntry) -> Result<PathBuf> {
151 let name = entry.file_name();
152 let name = name
153 .to_str()
154 .ok_or_else(|| anyhow!("file name is invalid UTF-8"))?;
155 let path = path.join(name);
156 Ok(path)
157}
158
159fn check_path_json(path: &Path) -> Result<()> {
165 if !path.is_file() {
166 return Err(anyhow!("path is not a file: {:?}", path));
167 }
168 if !path.extension().map_or(false, |ext| ext == "json") {
169 return Err(anyhow!("path is not a JSON file: {:?}", path));
170 }
171 Ok(())
172}