improved error handling
All checks were successful
Version Check / check-version (pull_request) Successful in 3s
All checks were successful
Version Check / check-version (pull_request) Successful in 3s
This commit is contained in:
@@ -1,14 +1,10 @@
|
|||||||
use std::io;
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
/// Custom error types for the mould application.
|
/// Custom error types for the mould application.
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum MouldError {
|
pub enum MouldError {
|
||||||
#[error("IO error: {0}")]
|
#[error("IO error: {0}")]
|
||||||
Io(#[from] io::Error),
|
Io(#[from] std::io::Error),
|
||||||
|
|
||||||
#[error("Format error: {0}")]
|
|
||||||
Format(String),
|
|
||||||
|
|
||||||
#[error("File not found: {0}")]
|
#[error("File not found: {0}")]
|
||||||
FileNotFound(String),
|
FileNotFound(String),
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
use super::{ConfigItem, FormatHandler, ItemStatus, ValueType};
|
use super::{ConfigItem, FormatHandler, ItemStatus, ValueType};
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::io::{self, Write};
|
use std::io::Write;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
pub struct EnvHandler;
|
pub struct EnvHandler;
|
||||||
|
|
||||||
impl FormatHandler for EnvHandler {
|
impl FormatHandler for EnvHandler {
|
||||||
fn parse(&self, path: &Path) -> io::Result<Vec<ConfigItem>> {
|
fn parse(&self, path: &Path) -> anyhow::Result<Vec<ConfigItem>> {
|
||||||
let content = fs::read_to_string(path)?;
|
let content = fs::read_to_string(path)?;
|
||||||
let mut vars = Vec::new();
|
let mut vars = Vec::new();
|
||||||
|
|
||||||
@@ -35,7 +35,7 @@ impl FormatHandler for EnvHandler {
|
|||||||
Ok(vars)
|
Ok(vars)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write(&self, path: &Path, vars: &[ConfigItem]) -> io::Result<()> {
|
fn write(&self, path: &Path, vars: &[ConfigItem]) -> anyhow::Result<()> {
|
||||||
let mut file = fs::File::create(path)?;
|
let mut file = fs::File::create(path)?;
|
||||||
for var in vars {
|
for var in vars {
|
||||||
if !var.is_group {
|
if !var.is_group {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
use super::{ConfigItem, FormatHandler, FormatType, ItemStatus, ValueType};
|
use super::{ConfigItem, FormatHandler, FormatType, ItemStatus, ValueType};
|
||||||
use serde_json::{Map, Value};
|
use serde_json::{Map, Value};
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::io;
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
pub struct HierarchicalHandler {
|
pub struct HierarchicalHandler {
|
||||||
@@ -13,50 +12,40 @@ impl HierarchicalHandler {
|
|||||||
Self { format_type }
|
Self { format_type }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_value(&self, path: &Path) -> io::Result<Value> {
|
fn read_value(&self, path: &Path) -> anyhow::Result<Value> {
|
||||||
let content = fs::read_to_string(path)?;
|
let content = fs::read_to_string(path)?;
|
||||||
let value = match self.format_type {
|
let value = match self.format_type {
|
||||||
FormatType::Json => serde_json::from_str(&content)
|
FormatType::Json => serde_json::from_str(&content)?,
|
||||||
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?,
|
FormatType::Yaml => serde_yaml::from_str(&content)?,
|
||||||
FormatType::Yaml => serde_yaml::from_str(&content)
|
FormatType::Toml => toml::from_str(&content)?,
|
||||||
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?,
|
FormatType::Xml => xml_to_json(&content)?,
|
||||||
FormatType::Toml => toml::from_str(&content)
|
|
||||||
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?,
|
|
||||||
FormatType::Xml => xml_to_json(&content)
|
|
||||||
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?,
|
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
Ok(value)
|
Ok(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_value(&self, path: &Path, value: &Value) -> io::Result<()> {
|
fn write_value(&self, path: &Path, value: &Value) -> anyhow::Result<()> {
|
||||||
let content = match self.format_type {
|
let content = match self.format_type {
|
||||||
FormatType::Json => serde_json::to_string_pretty(value)
|
FormatType::Json => serde_json::to_string_pretty(value)?,
|
||||||
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?,
|
FormatType::Yaml => serde_yaml::to_string(value)?,
|
||||||
FormatType::Yaml => serde_yaml::to_string(value)
|
|
||||||
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?,
|
|
||||||
FormatType::Toml => {
|
FormatType::Toml => {
|
||||||
// toml requires the root to be a table
|
// toml requires the root to be a table
|
||||||
if value.is_object() {
|
if value.is_object() {
|
||||||
let toml_value: toml::Value = serde_json::from_value(value.clone())
|
let toml_value: toml::Value = serde_json::from_value(value.clone())?;
|
||||||
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
|
toml::to_string_pretty(&toml_value)?
|
||||||
toml::to_string_pretty(&toml_value)
|
|
||||||
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?
|
|
||||||
} else {
|
} else {
|
||||||
return Err(io::Error::new(
|
anyhow::bail!("Root of TOML must be an object");
|
||||||
io::ErrorKind::InvalidData,
|
|
||||||
"Root of TOML must be an object",
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
FormatType::Xml => json_to_xml(value),
|
FormatType::Xml => json_to_xml(value),
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
fs::write(path, content)
|
fs::write(path, content)?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn xml_to_json(content: &str) -> io::Result<Value> {
|
fn xml_to_json(content: &str) -> anyhow::Result<Value> {
|
||||||
use quick_xml::reader::Reader;
|
use quick_xml::reader::Reader;
|
||||||
use quick_xml::events::Event;
|
use quick_xml::events::Event;
|
||||||
|
|
||||||
@@ -64,7 +53,7 @@ fn xml_to_json(content: &str) -> io::Result<Value> {
|
|||||||
reader.config_mut().trim_text(true);
|
reader.config_mut().trim_text(true);
|
||||||
let mut buf = Vec::new();
|
let mut buf = Vec::new();
|
||||||
|
|
||||||
fn parse_recursive(reader: &mut Reader<&[u8]>) -> io::Result<Value> {
|
fn parse_recursive(reader: &mut Reader<&[u8]>) -> anyhow::Result<Value> {
|
||||||
let mut map = Map::new();
|
let mut map = Map::new();
|
||||||
let mut text = String::new();
|
let mut text = String::new();
|
||||||
let mut buf = Vec::new();
|
let mut buf = Vec::new();
|
||||||
@@ -276,14 +265,14 @@ fn flatten(value: &Value, prefix: &str, depth: usize, key_name: &str, vars: &mut
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl FormatHandler for HierarchicalHandler {
|
impl FormatHandler for HierarchicalHandler {
|
||||||
fn parse(&self, path: &Path) -> io::Result<Vec<ConfigItem>> {
|
fn parse(&self, path: &Path) -> anyhow::Result<Vec<ConfigItem>> {
|
||||||
let value = self.read_value(path)?;
|
let value = self.read_value(path)?;
|
||||||
let mut vars = Vec::new();
|
let mut vars = Vec::new();
|
||||||
flatten(&value, "", 0, "", &mut vars);
|
flatten(&value, "", 0, "", &mut vars);
|
||||||
Ok(vars)
|
Ok(vars)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write(&self, path: &Path, vars: &[ConfigItem]) -> io::Result<()> {
|
fn write(&self, path: &Path, vars: &[ConfigItem]) -> anyhow::Result<()> {
|
||||||
let mut root = Value::Object(Map::new());
|
let mut root = Value::Object(Map::new());
|
||||||
for var in vars {
|
for var in vars {
|
||||||
if !var.is_group {
|
if !var.is_group {
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
use super::{ConfigItem, FormatHandler, ItemStatus, ValueType};
|
use super::{ConfigItem, FormatHandler, ItemStatus, ValueType};
|
||||||
use ini::Ini;
|
use ini::Ini;
|
||||||
use std::io;
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
pub struct IniHandler;
|
pub struct IniHandler;
|
||||||
|
|
||||||
impl FormatHandler for IniHandler {
|
impl FormatHandler for IniHandler {
|
||||||
fn parse(&self, path: &Path) -> io::Result<Vec<ConfigItem>> {
|
fn parse(&self, path: &Path) -> anyhow::Result<Vec<ConfigItem>> {
|
||||||
let conf = Ini::load_from_file(path)
|
let conf = Ini::load_from_file(path)?;
|
||||||
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
|
|
||||||
let mut vars = Vec::new();
|
let mut vars = Vec::new();
|
||||||
|
|
||||||
for (section, prop) in &conf {
|
for (section, prop) in &conf {
|
||||||
@@ -52,7 +50,7 @@ impl FormatHandler for IniHandler {
|
|||||||
Ok(vars)
|
Ok(vars)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write(&self, path: &Path, vars: &[ConfigItem]) -> io::Result<()> {
|
fn write(&self, path: &Path, vars: &[ConfigItem]) -> anyhow::Result<()> {
|
||||||
let mut conf = Ini::new();
|
let mut conf = Ini::new();
|
||||||
for var in vars {
|
for var in vars {
|
||||||
if !var.is_group {
|
if !var.is_group {
|
||||||
@@ -67,8 +65,8 @@ impl FormatHandler for IniHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
conf.write_to_file(path)
|
conf.write_to_file(path)?;
|
||||||
.map_err(io::Error::other)
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
use std::io;
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
pub mod env;
|
pub mod env;
|
||||||
@@ -46,8 +45,8 @@ pub enum FormatType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub trait FormatHandler {
|
pub trait FormatHandler {
|
||||||
fn parse(&self, path: &Path) -> io::Result<Vec<ConfigItem>>;
|
fn parse(&self, path: &Path) -> anyhow::Result<Vec<ConfigItem>>;
|
||||||
fn merge(&self, path: &Path, vars: &mut Vec<ConfigItem>) -> io::Result<()> {
|
fn merge(&self, path: &Path, vars: &mut Vec<ConfigItem>) -> anyhow::Result<()> {
|
||||||
if !path.exists() {
|
if !path.exists() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
@@ -82,7 +81,7 @@ pub trait FormatHandler {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
fn write(&self, path: &Path, vars: &[ConfigItem]) -> io::Result<()>;
|
fn write(&self, path: &Path, vars: &[ConfigItem]) -> anyhow::Result<()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn detect_format(path: &Path, override_format: Option<String>) -> FormatType {
|
pub fn detect_format(path: &Path, override_format: Option<String>) -> FormatType {
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
use super::{ConfigItem, FormatHandler, ItemStatus, ValueType};
|
use super::{ConfigItem, FormatHandler, ItemStatus, ValueType};
|
||||||
use java_properties::{LineContent, PropertiesIter, PropertiesWriter};
|
use java_properties::{LineContent, PropertiesIter, PropertiesWriter};
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{self, BufReader, BufWriter};
|
use std::io::{BufReader, BufWriter};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
pub struct PropertiesHandler;
|
pub struct PropertiesHandler;
|
||||||
|
|
||||||
impl FormatHandler for PropertiesHandler {
|
impl FormatHandler for PropertiesHandler {
|
||||||
fn parse(&self, path: &Path) -> io::Result<Vec<ConfigItem>> {
|
fn parse(&self, path: &Path) -> anyhow::Result<Vec<ConfigItem>> {
|
||||||
let file = File::open(path)?;
|
let file = File::open(path)?;
|
||||||
let reader = BufReader::new(file);
|
let reader = BufReader::new(file);
|
||||||
let iter = PropertiesIter::new(reader);
|
let iter = PropertiesIter::new(reader);
|
||||||
@@ -16,7 +16,7 @@ impl FormatHandler for PropertiesHandler {
|
|||||||
let mut groups = std::collections::HashSet::new();
|
let mut groups = std::collections::HashSet::new();
|
||||||
|
|
||||||
for line_result in iter {
|
for line_result in iter {
|
||||||
let line = line_result.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
|
let line = line_result?;
|
||||||
|
|
||||||
if let LineContent::KVPair(path, value) = line.consume_content() {
|
if let LineContent::KVPair(path, value) = line.consume_content() {
|
||||||
// Add groups based on dot notation
|
// Add groups based on dot notation
|
||||||
@@ -62,7 +62,7 @@ impl FormatHandler for PropertiesHandler {
|
|||||||
Ok(vars)
|
Ok(vars)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write(&self, path: &Path, vars: &[ConfigItem]) -> io::Result<()> {
|
fn write(&self, path: &Path, vars: &[ConfigItem]) -> anyhow::Result<()> {
|
||||||
let file = File::create(path)?;
|
let file = File::create(path)?;
|
||||||
let writer = BufWriter::new(file);
|
let writer = BufWriter::new(file);
|
||||||
let mut prop_writer = PropertiesWriter::new(writer);
|
let mut prop_writer = PropertiesWriter::new(writer);
|
||||||
@@ -72,12 +72,12 @@ impl FormatHandler for PropertiesHandler {
|
|||||||
let val = var.value.as_deref()
|
let val = var.value.as_deref()
|
||||||
.or(var.template_value.as_deref())
|
.or(var.template_value.as_deref())
|
||||||
.unwrap_or("");
|
.unwrap_or("");
|
||||||
prop_writer.write(&var.path, val)
|
prop_writer.write(&var.path, val)?;
|
||||||
.map_err(io::Error::other)?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
prop_writer.finish().map_err(io::Error::other)
|
prop_writer.finish()?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -96,10 +96,7 @@ fn main() -> anyhow::Result<()> {
|
|||||||
}
|
}
|
||||||
} else if vars.is_empty() {
|
} else if vars.is_empty() {
|
||||||
// Fallback if no template and active is empty
|
// Fallback if no template and active is empty
|
||||||
vars = handler.parse(&input_path).map_err(|e| {
|
vars = handler.parse(&input_path)?;
|
||||||
error!("Failed to parse input file: {}", e);
|
|
||||||
MouldError::Format(format!("Failed to parse {}: {}", input_path.display(), e))
|
|
||||||
})?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if vars.is_empty() {
|
if vars.is_empty() {
|
||||||
|
|||||||
Reference in New Issue
Block a user