1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279
// INFO.rs
// by Lut99
//
// Created:
// 28 Feb 2023, 10:07:36
// Last edited:
// 14 Jun 2024, 15:12:07
// Auto updated?
// Yes
//
// Description:
//! Defines the general [`Info`]-trait, which is used to abstract over the
//! various types of disk-stored configuration files.
//
use std::error::Error;
use std::fmt::{Debug, Display, Formatter, Result as FResult};
use std::fs::File;
use std::io::{Read, Write};
use std::path::{Path, PathBuf};
use async_trait::async_trait;
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use tokio::fs::File as TFile;
use tokio::io::AsyncReadExt as _;
/***** ERRORS *****/
/// Defines general errors for configs.
#[derive(Debug)]
pub enum InfoError<E: Debug> {
/// Failed to create the output file.
OutputCreateError { path: PathBuf, err: std::io::Error },
/// Failed to open the input file.
InputOpenError { path: PathBuf, err: std::io::Error },
/// Failed to read the input file.
InputReadError { path: PathBuf, err: std::io::Error },
/// Failed to serialize the config to a string.
StringSerializeError { err: E },
/// Failed to serialize the config to a given writer.
WriterSerializeError { err: E },
/// Failed to serialize the config to a given file.
FileSerializeError { path: PathBuf, err: E },
/// Failed to deserialize a string to the config.
StringDeserializeError { err: E },
/// Failed to deserialize a reader to the config.
ReaderDeserializeError { err: E },
/// Failed to deserialize a file to the config.
FileDeserializeError { path: PathBuf, err: E },
}
impl<E: Error> Display for InfoError<E> {
fn fmt(&self, f: &mut Formatter<'_>) -> FResult {
use InfoError::*;
match self {
OutputCreateError { path, .. } => write!(f, "Failed to create output file '{}'", path.display()),
InputOpenError { path, .. } => write!(f, "Failed to open input file '{}'", path.display()),
InputReadError { path, .. } => write!(f, "Failed to read input file '{}'", path.display()),
StringSerializeError { .. } => write!(f, "Failed to serialize to string"),
WriterSerializeError { .. } => write!(f, "Failed to serialize to a writer"),
FileSerializeError { path, .. } => write!(f, "Failed to serialize to output file '{}'", path.display()),
StringDeserializeError { .. } => write!(f, "Failed to deserialize from string"),
ReaderDeserializeError { .. } => write!(f, "Failed to deserialize from a reader"),
FileDeserializeError { path, .. } => write!(f, "Failed to deserialize from input file '{}'", path.display()),
}
}
}
impl<E: 'static + Error> Error for InfoError<E> {
fn source(&self) -> Option<&(dyn Error + 'static)> {
use InfoError::*;
match self {
OutputCreateError { err, .. } => Some(err),
InputOpenError { err, .. } => Some(err),
InputReadError { err, .. } => Some(err),
StringSerializeError { err } => Some(err),
WriterSerializeError { err } => Some(err),
FileSerializeError { err, .. } => Some(err),
StringDeserializeError { err } => Some(err),
ReaderDeserializeError { err } => Some(err),
FileDeserializeError { err, .. } => Some(err),
}
}
}
/***** LIBRARY *****/
/// Defines a serializable struct that we typically use for structs that are directly read and written to disk.
#[async_trait]
pub trait Info: Clone + Debug {
/// The types of errors that may be thrown by the serialization function(s).
type Error: Error;
// Child-provided
/// Serializes this Config to a string.
///
/// # Arguments
/// - `pretty`: If true, then it will be serialized using a pretty version of the backend (if available).
///
/// # Returns
/// A new String that represents this config but serialized.
///
/// # Errors
/// This function may error if the serialization failed.
fn to_string(&self, pretty: bool) -> Result<String, InfoError<Self::Error>>;
/// Serializes this Config to a reader.
///
/// # Arguments
/// - `writer`: The `Write`r to write the serialized representation to.
/// - `pretty`: If true, then it will be serialized using a pretty version of the backend (if available).
///
/// # Errors
/// This function may error if the serialization failed or if we failed to write to the given writer.
fn to_writer(&self, writer: impl Write, pretty: bool) -> Result<(), InfoError<Self::Error>>;
/// Deserializes the given string to an instance of ourselves.
///
/// # Arguments
/// - `raw`: The raw string to deserialize.
///
/// # Returns
/// A new instance of `Self` with its contents read from the given raw string.
///
/// # Errors
/// This function may fail if the input string was invalid for this object.
fn from_string(raw: impl AsRef<str>) -> Result<Self, InfoError<Self::Error>>;
/// Deserializes the contents of the given reader to an instance of ourselves.
///
/// # Arguments
/// - `reader`: The `Read`er who's contents to deserialize.
///
/// # Returns
/// A new instance of `Self` with its contents read from the given reader.
///
/// # Errors
/// This function may fail if we failed to read from the reader or if its contents were invalid for this object.
fn from_reader(reader: impl Read) -> Result<Self, InfoError<Self::Error>>;
// Globally deduced
/// Serializes this Config to a file at the given path.
///
/// This will always choose a pretty representation of the serialization (if applicable).
///
/// # Arguments
/// - `path`: The path where to write the file to.
///
/// # Errors
/// This function may error if the serialization failed or if we failed to create and/or write to the file.
fn to_path(&self, path: impl AsRef<Path>) -> Result<(), InfoError<Self::Error>> {
let path: &Path = path.as_ref();
// Attempt to create the new file
let handle: File = match File::create(path) {
Ok(handle) => handle,
Err(err) => {
return Err(InfoError::OutputCreateError { path: path.into(), err });
},
};
// Write it using the child function, wrapping the error that may occur
match self.to_writer(handle, true) {
Ok(_) => Ok(()),
Err(InfoError::WriterSerializeError { err }) => Err(InfoError::FileSerializeError { path: path.into(), err }),
Err(err) => Err(err),
}
}
/// Deserializes this Config from the file at the given path.
///
/// # Arguments
/// - `path`: The path where to read the file from.
///
/// # Errors
/// This function may fail if we failed to open/read from the file or if its contents were invalid for this object.
fn from_path(path: impl AsRef<Path>) -> Result<Self, InfoError<Self::Error>> {
let path: &Path = path.as_ref();
// Attempt to open the given file
let handle: File = match File::open(path) {
Ok(handle) => handle,
Err(err) => {
return Err(InfoError::InputOpenError { path: path.into(), err });
},
};
// Write it using the child function, wrapping the error that may occur
match Self::from_reader(handle) {
Ok(config) => Ok(config),
Err(InfoError::ReaderDeserializeError { err }) => Err(InfoError::FileDeserializeError { path: path.into(), err }),
Err(err) => Err(err),
}
}
/// Deserializes this Config from the file at the given path, with the reading part done asynchronously.
///
/// Note that the parsing path cannot be done asynchronously. Also, note that, because serde does not support asynchronous deserialization, we have to read the entire file in one go.
///
/// # Arguments
/// - `path`: The path where to read the file from.
///
/// # Errors
/// This function may fail if we failed to open/read from the file or if its contents were invalid for this object.
async fn from_path_async(path: impl Send + AsRef<Path>) -> Result<Self, InfoError<Self::Error>> {
let path: &Path = path.as_ref();
// Read the file to a string
let raw: String = {
// Attempt to open the given file
let mut handle: TFile = match TFile::open(path).await {
Ok(handle) => handle,
Err(err) => {
return Err(InfoError::InputOpenError { path: path.into(), err });
},
};
// Read everything to a string
let mut raw: String = String::new();
if let Err(err) = handle.read_to_string(&mut raw).await {
return Err(InfoError::InputReadError { path: path.into(), err });
}
raw
};
// Write it using the child function, wrapping the error that may occur
match Self::from_string(raw) {
Ok(config) => Ok(config),
Err(InfoError::ReaderDeserializeError { err }) => Err(InfoError::FileDeserializeError { path: path.into(), err }),
Err(err) => Err(err),
}
}
}
/// A marker trait that will let the compiler implement `Config` for this object using the `serde_yaml` backend.
pub trait YamlInfo<'de>: Clone + Debug + Deserialize<'de> + Serialize {}
impl<T: DeserializeOwned + Serialize + for<'de> YamlInfo<'de>> Info for T {
type Error = serde_yaml::Error;
fn to_string(&self, _pretty: bool) -> Result<String, InfoError<Self::Error>> {
match serde_yaml::to_string(self) {
Ok(raw) => Ok(raw),
Err(err) => Err(InfoError::StringSerializeError { err }),
}
}
fn to_writer(&self, writer: impl Write, _pretty: bool) -> Result<(), InfoError<Self::Error>> {
match serde_yaml::to_writer(writer, self) {
Ok(raw) => Ok(raw),
Err(err) => Err(InfoError::ReaderDeserializeError { err }),
}
}
fn from_string(raw: impl AsRef<str>) -> Result<Self, InfoError<Self::Error>> {
match serde_yaml::from_str(raw.as_ref()) {
Ok(config) => Ok(config),
Err(err) => Err(InfoError::StringDeserializeError { err }),
}
}
fn from_reader(reader: impl Read) -> Result<Self, InfoError<Self::Error>> {
match serde_yaml::from_reader(reader) {
Ok(config) => Ok(config),
Err(err) => Err(InfoError::ReaderDeserializeError { err }),
}
}
}
/// A type alias for the ConfigError for the YamlConfig.
pub type YamlError = InfoError<serde_yaml::Error>;