mirror of
https://codeberg.org/Toasterson/ips.git
synced 2026-04-10 21:30:41 +00:00
Better error handling
This commit is contained in:
parent
cb2faef0d3
commit
eafbb834a5
3 changed files with 141 additions and 85 deletions
|
|
@ -13,3 +13,4 @@ edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
regex = "1.3.7"
|
regex = "1.3.7"
|
||||||
|
failure = "0.1.8"
|
||||||
|
|
@ -10,6 +10,7 @@ use std::fmt;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::BufRead;
|
use std::io::BufRead;
|
||||||
use std::io::BufReader;
|
use std::io::BufReader;
|
||||||
|
use failure::Error;
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct Dir {
|
pub struct Dir {
|
||||||
|
|
@ -55,93 +56,144 @@ enum ActionKind {
|
||||||
Driver,
|
Driver,
|
||||||
License,
|
License,
|
||||||
Link,
|
Link,
|
||||||
|
Legacy,
|
||||||
|
Unknown{action: String},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
//TODO Multierror and no failure for these cases
|
||||||
|
#[derive(Debug, Fail)]
|
||||||
pub enum ManifestError {
|
pub enum ManifestError {
|
||||||
EmptyVec,
|
#[fail(display = "unknown action {} at line {}", action, line)]
|
||||||
// We will defer to the parse error implementation for their error.
|
UnknownAction {
|
||||||
// Supplying extra info requires adding more data to the type.
|
line: usize,
|
||||||
Read(std::io::Error),
|
action: String,
|
||||||
Regex(regex::Error),
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for ManifestError {
|
pub fn parse_manifest_file(filename: String) -> Result<Manifest, Error> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
let mut m = Manifest::new();
|
||||||
match *self {
|
let f = File::open(filename)?;
|
||||||
ManifestError::EmptyVec => write!(f, "please use a vector with at least one element"),
|
|
||||||
// This is a wrapper, so defer to the underlying types' implementation of `fmt`.
|
|
||||||
ManifestError::Read(ref e) => e.fmt(f),
|
|
||||||
ManifestError::Regex(ref e) => e.fmt(f),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl error::Error for ManifestError {
|
|
||||||
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
|
|
||||||
match *self {
|
|
||||||
ManifestError::EmptyVec => None,
|
|
||||||
// The cause is the underlying implementation error type. Is implicitly
|
|
||||||
// cast to the trait object `&error::Error`. This works because the
|
|
||||||
// underlying type already implements the `Error` trait.
|
|
||||||
ManifestError::Read(ref e) => Some(e),
|
|
||||||
ManifestError::Regex(ref e) => Some(e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parse_manifest_file(filename: String) -> Result<Manifest, ManifestError> {
|
|
||||||
let mut manifest = Manifest::new();
|
|
||||||
let f = match File::open(filename) {
|
|
||||||
Ok(file) => file,
|
|
||||||
Err(e) => return Err(ManifestError::Read(e)),
|
|
||||||
};
|
|
||||||
let file = BufReader::new(&f);
|
let file = BufReader::new(&f);
|
||||||
for line_read in file.lines() {
|
|
||||||
let line = match line_read {
|
for (line_nr, line_read) in file.lines().enumerate() {
|
||||||
Ok(l) => l,
|
let line = line_read?;
|
||||||
Err(e) => return Err(ManifestError::Read(e)),
|
match determine_action_kind(&line) {
|
||||||
};
|
ActionKind::Attr => {
|
||||||
if is_attr_action(&line) {
|
let attr = parse_attr_action(String::from(line))?;
|
||||||
match parse_attr_action(line) {
|
m.attributes.push(attr)
|
||||||
Ok(attr) => manifest.attributes.push(attr),
|
}
|
||||||
Err(e) => return Err(e),
|
ActionKind::Dir => {
|
||||||
|
|
||||||
|
}
|
||||||
|
ActionKind::File => {
|
||||||
|
|
||||||
|
}
|
||||||
|
ActionKind::Dependency => {
|
||||||
|
|
||||||
|
}
|
||||||
|
ActionKind::User => {
|
||||||
|
|
||||||
|
}
|
||||||
|
ActionKind::Group => {
|
||||||
|
|
||||||
|
}
|
||||||
|
ActionKind::Driver => {
|
||||||
|
|
||||||
|
}
|
||||||
|
ActionKind::License => {
|
||||||
|
|
||||||
|
}
|
||||||
|
ActionKind::Link => {
|
||||||
|
|
||||||
|
}
|
||||||
|
ActionKind::Legacy => {
|
||||||
|
|
||||||
|
}
|
||||||
|
ActionKind::Unknown{action} => {
|
||||||
|
Err(ManifestError::UnknownAction {action, line: line_nr})?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Ok(manifest);
|
|
||||||
|
return Ok(m);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_manifest_string(manifest: String) -> Result<Manifest, ManifestError> {
|
pub fn parse_manifest_string(manifest: String) -> Result<Manifest, Error> {
|
||||||
let mut m = Manifest::new();
|
let mut m = Manifest::new();
|
||||||
for line in manifest.lines() {
|
for (line_nr, line) in manifest.lines().enumerate() {
|
||||||
if is_attr_action(&String::from(line)) {
|
match determine_action_kind(&line) {
|
||||||
match parse_attr_action(String::from(line)) {
|
ActionKind::Attr => {
|
||||||
Ok(attr) => m.attributes.push(attr),
|
let attr = parse_attr_action(String::from(line))?;
|
||||||
Err(e) => return Err(e),
|
m.attributes.push(attr)
|
||||||
};
|
}
|
||||||
|
ActionKind::Dir => {
|
||||||
|
|
||||||
|
}
|
||||||
|
ActionKind::File => {
|
||||||
|
|
||||||
|
}
|
||||||
|
ActionKind::Dependency => {
|
||||||
|
|
||||||
|
}
|
||||||
|
ActionKind::User => {
|
||||||
|
|
||||||
|
}
|
||||||
|
ActionKind::Group => {
|
||||||
|
|
||||||
|
}
|
||||||
|
ActionKind::Driver => {
|
||||||
|
|
||||||
|
}
|
||||||
|
ActionKind::License => {
|
||||||
|
|
||||||
|
}
|
||||||
|
ActionKind::Link => {
|
||||||
|
|
||||||
|
}
|
||||||
|
ActionKind::Legacy => {
|
||||||
|
|
||||||
|
}
|
||||||
|
ActionKind::Unknown{action} => {
|
||||||
|
Err(ManifestError::UnknownAction {action, line: line_nr})?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Ok(m);
|
return Ok(m);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_attr_action(line: &String) -> bool {
|
fn determine_action_kind(line: &str) -> ActionKind {
|
||||||
if line.trim().starts_with("set ") {
|
let mut act = String::new();
|
||||||
return true;
|
for c in line.trim_start().chars() {
|
||||||
|
if c == ' ' {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
act.push(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
return match act.as_str() {
|
||||||
|
"set" => ActionKind::Attr,
|
||||||
|
"depend" => ActionKind::Dependency,
|
||||||
|
"dir" => ActionKind::Dir,
|
||||||
|
"file" => ActionKind::File,
|
||||||
|
"license" => ActionKind::License,
|
||||||
|
"hardlink" => ActionKind::Link,
|
||||||
|
"link" => ActionKind::Link,
|
||||||
|
"driver" => ActionKind::Driver,
|
||||||
|
"group" => ActionKind::Group,
|
||||||
|
"user" => ActionKind::User,
|
||||||
|
"legacy" => ActionKind::Legacy,
|
||||||
|
_ => ActionKind::Unknown{action: act},
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_attr_action(line: String) -> Result<Attr, ManifestError> {
|
pub fn parse_attr_action(line: String) -> Result<Attr, Error> {
|
||||||
// Do a full line match to see if we can fast path this.
|
// Do a full line match to see if we can fast path this.
|
||||||
// This also catches values with spaces, that have not been properly escaped.
|
// This also catches values with spaces, that have not been properly escaped.
|
||||||
// Note: values with spaces must be properly escaped or the rest here will fail. Strings with
|
// Note: values with spaces must be properly escaped or the rest here will fail. Strings with
|
||||||
// unescaped spaces are never valid but sadly present in the wild.
|
// unescaped spaces are never valid but sadly present in the wild.
|
||||||
// Fast path will fail if a value has multiple values or a '=' sign in the values
|
// Fast path will fail if a value has multiple values or a '=' sign in the values
|
||||||
let full_line_regex = match Regex::new(r"^set name=([^ ]+) value=(.+)$") {
|
let full_line_regex = Regex::new(r"^set name=([^ ]+) value=(.+)$")?;
|
||||||
Ok(re) => re,
|
|
||||||
Err(e) => return Err(ManifestError::Regex(e)),
|
|
||||||
};
|
|
||||||
|
|
||||||
if full_line_regex.is_match(line.trim_start()) {
|
if full_line_regex.is_match(line.trim_start()) {
|
||||||
match full_line_regex.captures(line.trim_start()) {
|
match full_line_regex.captures(line.trim_start()) {
|
||||||
|
|
@ -162,6 +214,7 @@ pub fn parse_attr_action(line: String) -> Result<Attr, ManifestError> {
|
||||||
}
|
}
|
||||||
|
|
||||||
val = val.replace(&['"', '\\'][..], "");
|
val = val.replace(&['"', '\\'][..], "");
|
||||||
|
//TODO knock out single quotes somehow
|
||||||
|
|
||||||
if !fast_path_fail{
|
if !fast_path_fail{
|
||||||
return Ok(Attr{
|
return Ok(Attr{
|
||||||
|
|
@ -177,36 +230,21 @@ pub fn parse_attr_action(line: String) -> Result<Attr, ManifestError> {
|
||||||
|
|
||||||
|
|
||||||
//Todo move regex initialisation out of for loop into static area
|
//Todo move regex initialisation out of for loop into static area
|
||||||
let name_regex = match Regex::new(r"name=([^ ]+) value=") {
|
let name_regex = Regex::new(r"name=([^ ]+) value=")?;
|
||||||
Ok(re) => re,
|
|
||||||
Err(e) => return Err(ManifestError::Regex(e)),
|
|
||||||
};
|
|
||||||
let mut key = String::new();
|
let mut key = String::new();
|
||||||
for cap in name_regex.captures_iter(line.trim_start()) {
|
for cap in name_regex.captures_iter(line.trim_start()) {
|
||||||
key = String::from(&cap[1]);
|
key = String::from(&cap[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut values = Vec::new();
|
let mut values = Vec::new();
|
||||||
let value_no_space_regex = match Regex::new(r#"value="(.+)""#) {
|
let value_no_space_regex = Regex::new(r#"value="(.+)""#)?;
|
||||||
Ok(re) => re,
|
|
||||||
Err(e) => return Err(ManifestError::Regex(e)),
|
|
||||||
};
|
|
||||||
|
|
||||||
let value_space_regex = match Regex::new(r#"value=([^"][^ ]+[^"])"#) {
|
let value_space_regex = Regex::new(r#"value=([^"][^ ]+[^"])"#)?;
|
||||||
Ok(re) => re,
|
|
||||||
Err(e) => return Err(ManifestError::Regex(e)),
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut properties = HashSet::new();
|
let mut properties = HashSet::new();
|
||||||
let optionals_regex_no_quotes = match Regex::new(r#"([^ ]+)=([^"][^ ]+[^"])"#) {
|
let optionals_regex_no_quotes = Regex::new(r#"([^ ]+)=([^"][^ ]+[^"])"#)?;
|
||||||
Ok(re) => re,
|
|
||||||
Err(e) => return Err(ManifestError::Regex(e)),
|
|
||||||
};
|
|
||||||
|
|
||||||
let optionals_regex_quotes = match Regex::new(r#"([^ ]+)=([^"][^ ]+[^"])"#) {
|
let optionals_regex_quotes = Regex::new(r#"([^ ]+)=([^"][^ ]+[^"])"#)?;
|
||||||
Ok(re) => re,
|
|
||||||
Err(e) => return Err(ManifestError::Regex(e)),
|
|
||||||
};
|
|
||||||
|
|
||||||
for cap in value_no_space_regex.captures_iter(line.trim_start()) {
|
for cap in value_no_space_regex.captures_iter(line.trim_start()) {
|
||||||
values.push(String::from(cap[1].trim()));
|
values.push(String::from(cap[1].trim()));
|
||||||
|
|
|
||||||
23
src/lib.rs
23
src/lib.rs
|
|
@ -5,6 +5,8 @@
|
||||||
|
|
||||||
mod actions;
|
mod actions;
|
||||||
|
|
||||||
|
#[macro_use] extern crate failure;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|
||||||
|
|
@ -28,7 +30,9 @@ mod tests {
|
||||||
set name=pkg.summary value=\\\"provided mouse accessibility enhancements\\\"
|
set name=pkg.summary value=\\\"provided mouse accessibility enhancements\\\"
|
||||||
set name=info.upstream value=X.Org Foundation
|
set name=info.upstream value=X.Org Foundation
|
||||||
set name=pkg.description value=Latvian language support's extra files
|
set name=pkg.description value=Latvian language support's extra files
|
||||||
set name=variant.arch value=i386 optional=testing optionalWithString=\"test ing\"");
|
set name=variant.arch value=i386 optional=testing optionalWithString=\"test ing\"
|
||||||
|
set name=info.source-url value=http://www.pgpool.net/download.php?f=pgpool-II-3.3.1.tar.gz
|
||||||
|
set name=pkg.summary value=\\\"'XZ Utils - loss-less file compression application and library.'\\\"");
|
||||||
|
|
||||||
let mut optional_hash = HashSet::new();
|
let mut optional_hash = HashSet::new();
|
||||||
optional_hash.insert(Property{key: String::from("optional"), value:String::from("testing")});
|
optional_hash.insert(Property{key: String::from("optional"), value:String::from("testing")});
|
||||||
|
|
@ -109,16 +113,29 @@ mod tests {
|
||||||
key: String::from("variant.arch"),
|
key: String::from("variant.arch"),
|
||||||
values: vec![String::from("i386")],
|
values: vec![String::from("i386")],
|
||||||
properties: optional_hash,
|
properties: optional_hash,
|
||||||
|
},
|
||||||
|
Attr{
|
||||||
|
key: String::from("info.source-url"),
|
||||||
|
values: vec![String::from("http://www.pgpool.net/download.php?f=pgpool-II-3.3.1.tar.gz")],
|
||||||
|
properties: HashSet::new(),
|
||||||
|
},
|
||||||
|
Attr{
|
||||||
|
key: String::from("pkg.summary"),
|
||||||
|
values: vec![String::from("'XZ Utils - loss-less file compression application and library.'")], //TODO knock out the single quotes
|
||||||
|
properties: HashSet::new(),
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
let mut manifest = Manifest::new();
|
let mut manifest = Manifest::new();
|
||||||
match parse_manifest_string(manifest_string) {
|
match parse_manifest_string(manifest_string) {
|
||||||
Ok(m) => manifest = m,
|
Ok(m) => manifest = m,
|
||||||
Err(_) => assert!(false, "caught error"),
|
Err(e) => {
|
||||||
|
println!("{}", e);
|
||||||
|
assert!(false, "caught error");
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
assert_eq!(manifest.attributes.len(), 15);
|
assert_eq!(manifest.attributes.len(), 17);
|
||||||
|
|
||||||
for (pos, attr) in manifest.attributes.iter().enumerate() {
|
for (pos, attr) in manifest.attributes.iter().enumerate() {
|
||||||
assert_eq!(attr.key, test_results[pos].key);
|
assert_eq!(attr.key, test_results[pos].key);
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue