frontend-utils: Unify warning format strings; add warning enum

This also lays the groundwork for translated warnings.
This commit is contained in:
sleepycatcoding 2024-04-21 23:11:55 +03:00
parent 15c076fe0c
commit 86b35145ec
5 changed files with 163 additions and 77 deletions

View File

@ -1,5 +1,7 @@
use crate::preferences::SavedGlobalPreferences;
use ruffle_frontend_utils::parse::{DocumentHolder, ParseContext, ParseDetails, ReadExt};
use ruffle_frontend_utils::parse::{
DocumentHolder, ParseContext, ParseDetails, ParseWarning, ReadExt,
};
use toml_edit::DocumentMut;
/// Read the given preferences into a **guaranteed valid** `SavedGlobalPreferences`,
@ -17,7 +19,7 @@ pub fn read_preferences(input: &str) -> ParseDetails<SavedGlobalPreferences> {
Err(e) => {
return ParseDetails {
result: Default::default(),
warnings: vec![format!("Invalid TOML: {e}")],
warnings: vec![ParseWarning::InvalidToml(e)],
}
}
};
@ -80,7 +82,8 @@ mod tests {
let result = read_preferences("~~INVALID~~");
assert_eq!(&SavedGlobalPreferences::default(), result.values());
assert_eq!(vec!["Invalid TOML: TOML parse error at line 1, column 1\n |\n1 | ~~INVALID~~\n | ^\ninvalid key\n".to_string()], result.warnings);
assert_eq!(result.warnings.len(), 1);
assert_eq!("Invalid TOML: TOML parse error at line 1, column 1\n |\n1 | ~~INVALID~~\n | ^\ninvalid key\n", result.warnings[0].to_string());
}
#[test]
@ -88,7 +91,7 @@ mod tests {
let result = read_preferences("");
assert_eq!(&SavedGlobalPreferences::default(), result.values());
assert_eq!(Vec::<String>::new(), result.warnings);
assert_eq!(Vec::<ParseWarning>::new(), result.warnings);
}
#[test]
@ -97,7 +100,11 @@ mod tests {
assert_eq!(&SavedGlobalPreferences::default(), result.values());
assert_eq!(
vec!["Invalid graphics_backend: expected string but found integer".to_string()],
vec![ParseWarning::UnexpectedType {
expected: "string",
actual: "integer",
path: "graphics_backend".to_string()
}],
result.warnings
);
}
@ -108,7 +115,10 @@ mod tests {
assert_eq!(&SavedGlobalPreferences::default(), result.values());
assert_eq!(
vec!["Invalid graphics_backend: unsupported value \"fast\"".to_string()],
vec![ParseWarning::UnsupportedValue {
value: "fast".to_string(),
path: "graphics_backend".to_string()
}],
result.warnings
);
}
@ -124,7 +134,7 @@ mod tests {
},
result.values()
);
assert_eq!(Vec::<String>::new(), result.warnings);
assert_eq!(Vec::<ParseWarning>::new(), result.warnings);
}
#[test]
@ -133,9 +143,11 @@ mod tests {
assert_eq!(&SavedGlobalPreferences::default(), result.values());
assert_eq!(
vec![
"Invalid graphics_power_preference: expected string but found integer".to_string()
],
vec![ParseWarning::UnexpectedType {
expected: "string",
actual: "integer",
path: "graphics_power_preference".to_string()
}],
result.warnings
);
}
@ -146,7 +158,10 @@ mod tests {
assert_eq!(&SavedGlobalPreferences::default(), result.values());
assert_eq!(
vec!["Invalid graphics_power_preference: unsupported value \"fast\"".to_string()],
vec![ParseWarning::UnsupportedValue {
value: "fast".to_string(),
path: "graphics_power_preference".to_string()
}],
result.warnings
);
}
@ -162,7 +177,7 @@ mod tests {
},
result.values()
);
assert_eq!(Vec::<String>::new(), result.warnings);
assert_eq!(Vec::<ParseWarning>::new(), result.warnings);
}
#[test]
@ -171,7 +186,10 @@ mod tests {
assert_eq!(&SavedGlobalPreferences::default(), result.values());
assert_eq!(
vec!["Invalid language: unsupported value \"???\"".to_string()],
vec![ParseWarning::UnsupportedValue {
value: "???".to_string(),
path: "language".to_string()
}],
result.warnings
);
}
@ -187,7 +205,7 @@ mod tests {
},
result.values()
);
assert_eq!(Vec::<String>::new(), result.warnings);
assert_eq!(Vec::<ParseWarning>::new(), result.warnings);
}
#[test]
@ -201,7 +219,7 @@ mod tests {
},
result.values()
);
assert_eq!(Vec::<String>::new(), result.warnings);
assert_eq!(Vec::<ParseWarning>::new(), result.warnings);
}
#[test]
@ -216,7 +234,11 @@ mod tests {
result.values()
);
assert_eq!(
vec!["Invalid output_device: expected string but found integer".to_string()],
vec![ParseWarning::UnexpectedType {
expected: "string",
actual: "integer",
path: "output_device".to_string()
}],
result.warnings
);
}
@ -232,7 +254,11 @@ mod tests {
result.values()
);
assert_eq!(
vec!["Invalid mute: expected boolean but found string".to_string()],
vec![ParseWarning::UnexpectedType {
expected: "boolean",
actual: "string",
path: "mute".to_string()
}],
result.warnings
);
@ -244,7 +270,7 @@ mod tests {
},
result.values()
);
assert_eq!(Vec::<String>::new(), result.warnings);
assert_eq!(Vec::<ParseWarning>::new(), result.warnings);
let result = read_preferences("");
assert_eq!(
@ -254,7 +280,7 @@ mod tests {
},
result.values()
);
assert_eq!(Vec::<String>::new(), result.warnings);
assert_eq!(Vec::<ParseWarning>::new(), result.warnings);
}
#[test]
@ -268,7 +294,11 @@ mod tests {
result.values()
);
assert_eq!(
vec!["Invalid volume: expected float but found string".to_string()],
vec![ParseWarning::UnexpectedType {
expected: "float",
actual: "string",
path: "volume".to_string()
}],
result.warnings
);
@ -280,7 +310,7 @@ mod tests {
},
result.values()
);
assert_eq!(Vec::<String>::new(), result.warnings);
assert_eq!(Vec::<ParseWarning>::new(), result.warnings);
let result = read_preferences("volume = -1.0");
assert_eq!(
@ -290,7 +320,7 @@ mod tests {
},
result.values()
);
assert_eq!(Vec::<String>::new(), result.warnings);
assert_eq!(Vec::<ParseWarning>::new(), result.warnings);
}
#[test]
@ -306,14 +336,21 @@ mod tests {
result.values()
);
assert_eq!(
vec!["Invalid log.filename_pattern: expected string but found integer".to_string()],
vec![ParseWarning::UnexpectedType {
expected: "string",
actual: "integer",
path: "log.filename_pattern".to_string()
}],
result.warnings
);
let result = read_preferences("log = {filename_pattern = \"???\"}");
assert_eq!(&SavedGlobalPreferences::default(), result.values());
assert_eq!(
vec!["Invalid log.filename_pattern: unsupported value \"???\"".to_string()],
vec![ParseWarning::UnsupportedValue {
value: "???".to_string(),
path: "log.filename_pattern".to_string()
}],
result.warnings
);
@ -327,7 +364,7 @@ mod tests {
},
result.values()
);
assert_eq!(Vec::<String>::new(), result.warnings);
assert_eq!(Vec::<ParseWarning>::new(), result.warnings);
}
#[test]
@ -335,7 +372,11 @@ mod tests {
let result = read_preferences("log = \"yes\"");
assert_eq!(&SavedGlobalPreferences::default(), result.values());
assert_eq!(
vec!["Invalid log: expected table but found string".to_string()],
vec![ParseWarning::UnexpectedType {
expected: "table",
actual: "string",
path: "log".to_string()
}],
result.warnings
);
}
@ -345,14 +386,21 @@ mod tests {
let result = read_preferences("storage = {backend = 5}");
assert_eq!(&SavedGlobalPreferences::default(), result.values());
assert_eq!(
vec!["Invalid storage.backend: expected string but found integer".to_string()],
vec![ParseWarning::UnexpectedType {
expected: "string",
actual: "integer",
path: "storage.backend".to_string()
}],
result.warnings
);
let result = read_preferences("storage = {backend = \"???\"}");
assert_eq!(&SavedGlobalPreferences::default(), result.values());
assert_eq!(
vec!["Invalid storage.backend: unsupported value \"???\"".to_string()],
vec![ParseWarning::UnsupportedValue {
value: "???".to_string(),
path: "storage.backend".to_string()
}],
result.warnings
);
@ -366,7 +414,7 @@ mod tests {
},
result.values()
);
assert_eq!(Vec::<String>::new(), result.warnings);
assert_eq!(Vec::<ParseWarning>::new(), result.warnings);
}
#[test]
@ -374,7 +422,11 @@ mod tests {
let result = read_preferences("storage = \"no\"");
assert_eq!(&SavedGlobalPreferences::default(), result.values());
assert_eq!(
vec!["Invalid storage: expected table but found string".to_string()],
vec![ParseWarning::UnexpectedType {
expected: "table",
actual: "string",
path: "storage".to_string()
}],
result.warnings
);
}

View File

@ -1,5 +1,5 @@
use crate::bookmarks::{Bookmark, Bookmarks, INVALID_URL};
use crate::parse::{DocumentHolder, ParseContext, ParseDetails, ReadExt};
use crate::parse::{DocumentHolder, ParseContext, ParseDetails, ParseWarning, ReadExt};
use toml_edit::DocumentMut;
use url::Url;
@ -9,7 +9,7 @@ pub fn read_bookmarks(input: &str) -> ParseDetails<Bookmarks> {
Err(e) => {
return ParseDetails {
result: Default::default(),
warnings: vec![format!("Invalid TOML: {e}")],
warnings: vec![ParseWarning::InvalidToml(e)],
}
}
};
@ -50,7 +50,11 @@ mod tests {
let result = read_bookmarks("[bookmark]");
assert_eq!(&Vec::<Bookmark>::new(), result.values());
assert_eq!(
vec!["Invalid bookmark: expected array of tables but found table".to_string()],
vec![ParseWarning::UnexpectedType {
expected: "array of tables",
actual: "table",
path: "bookmark".to_string()
}],
result.warnings
);
@ -62,7 +66,7 @@ mod tests {
}],
result.values()
);
assert_eq!(Vec::<String>::new(), result.warnings);
assert_eq!(Vec::<ParseWarning>::new(), result.warnings);
let result = read_bookmarks("[[bookmark]]\nurl = \"invalid\"");
assert_eq!(
@ -73,7 +77,10 @@ mod tests {
result.values()
);
assert_eq!(
vec!["Invalid bookmark.url: unsupported value \"invalid\"".to_string()],
vec![ParseWarning::UnsupportedValue {
value: "invalid".to_string(),
path: "bookmark.url".to_string()
}],
result.warnings
);
@ -87,7 +94,7 @@ mod tests {
}],
result.values()
);
assert_eq!(Vec::<String>::new(), result.warnings);
assert_eq!(Vec::<ParseWarning>::new(), result.warnings);
}
#[test]
@ -114,7 +121,7 @@ mod tests {
],
result.values()
);
assert_eq!(Vec::<String>::new(), result.warnings);
assert_eq!(Vec::<ParseWarning>::new(), result.warnings);
let result = read_bookmarks(
r#"
@ -152,7 +159,10 @@ mod tests {
result.values()
);
assert_eq!(
vec!["Invalid bookmark.url: unsupported value \"invalid\"".to_string()],
vec![ParseWarning::UnsupportedValue {
value: "invalid".to_string(),
path: "bookmark.url".to_string()
}],
result.warnings
);
}

View File

@ -2,6 +2,7 @@ use crate::bundle::info::{
BundleInformation, BundleInformationParseError, BUNDLE_INFORMATION_FILENAME,
};
use crate::bundle::source::BundleSource;
use crate::parse::ParseWarning;
use std::path::Path;
pub mod info;
@ -25,7 +26,7 @@ pub enum BundleError {
pub struct Bundle {
source: BundleSource,
information: BundleInformation,
warnings: Vec<String>,
warnings: Vec<ParseWarning>,
}
impl Bundle {
@ -53,7 +54,7 @@ impl Bundle {
&self.source
}
pub fn warnings(&self) -> &[String] {
pub fn warnings(&self) -> &[ParseWarning] {
&self.warnings
}
@ -69,6 +70,7 @@ mod tests {
};
use crate::bundle::source::BundleSourceError;
use crate::bundle::{Bundle, BundleError};
use crate::parse::ParseWarning;
use tempfile::tempdir;
use url::Url;
@ -150,6 +152,6 @@ mod tests {
},
result.information
);
assert_eq!(Vec::<String>::new(), result.warnings);
assert_eq!(Vec::<ParseWarning>::new(), result.warnings);
}
}

View File

@ -55,9 +55,12 @@ impl BundleInformation {
#[cfg(test)]
mod test {
use crate::bundle::info::{BundleInformation, BundleInformationParseError};
use crate::parse::ParseWarning;
use url::Url;
fn read(input: &str) -> Result<(BundleInformation, Vec<String>), BundleInformationParseError> {
fn read(
input: &str,
) -> Result<(BundleInformation, Vec<ParseWarning>), BundleInformationParseError> {
BundleInformation::parse(input).map(|details| (details.result.take(), details.warnings))
}

View File

@ -2,7 +2,7 @@ use std::fmt;
use std::fmt::Formatter;
use std::ops::Deref;
use std::str::FromStr;
use toml_edit::{ArrayOfTables, DocumentMut, Item, Table, TableLike};
use toml_edit::{ArrayOfTables, DocumentMut, Item, Table, TableLike, TomlError};
/// A holder over values that may be read and potentially written back to disk.
///
@ -75,7 +75,7 @@ impl<T> DocumentHolder<T> {
pub struct ParseDetails<T> {
pub result: DocumentHolder<T>,
pub warnings: Vec<String>,
pub warnings: Vec<ParseWarning>,
}
impl<T: fmt::Debug> fmt::Debug for ParseDetails<T> {
@ -88,18 +88,44 @@ impl<T: fmt::Debug> fmt::Debug for ParseDetails<T> {
}
impl<T> ParseDetails<T> {
pub fn add_warning(&mut self, message: String) {
self.warnings.push(message);
}
pub fn values(&self) -> &T {
&self.result
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum ParseWarning {
InvalidToml(TomlError),
UnexpectedType {
expected: &'static str,
actual: &'static str,
path: String,
},
UnsupportedValue {
value: String,
path: String,
},
}
impl fmt::Display for ParseWarning {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
ParseWarning::InvalidToml(e) => write!(f, "Invalid TOML: {e}"),
ParseWarning::UnexpectedType {
expected,
actual,
path,
} => write!(f, "Invalid {path}: expected {expected} but found {actual}"),
ParseWarning::UnsupportedValue { value, path } => {
write!(f, "Invalid {path}: unsupported value {value:?}")
}
}
}
}
#[derive(Default)]
pub struct ParseContext {
pub warnings: Vec<String>,
pub warnings: Vec<ParseWarning>,
/// Path of the current item being parsed
path: Vec<&'static str>,
}
@ -117,8 +143,21 @@ impl ParseContext {
self.path.join(".")
}
pub fn add_warning(&mut self, warning: String) {
self.warnings.push(warning);
/// Emits an unexpected type warning.
pub fn unexpected_type(&mut self, expected: &'static str, actual: &'static str) {
self.warnings.push(ParseWarning::UnexpectedType {
expected,
actual,
path: self.path(),
})
}
/// Emits an unsupported value warning.
pub fn unsupported_value(&mut self, value: String) {
self.warnings.push(ParseWarning::UnsupportedValue {
value,
path: self.path(),
})
}
}
@ -138,11 +177,7 @@ pub trait ReadExt<'a> {
if let Some(table) = item.as_table_like() {
result = Some(fun(cx, table));
} else {
cx.add_warning(format!(
"Invalid {}: expected table but found {}",
cx.path(),
item.type_name()
));
cx.unexpected_type("table", item.type_name());
}
cx.pop_key();
@ -163,11 +198,7 @@ pub trait ReadExt<'a> {
if let Some(array) = item.as_array_of_tables() {
result = Some(fun(cx, array));
} else {
cx.add_warning(format!(
"Invalid {}: expected array of tables but found {}",
cx.path(),
item.type_name()
));
cx.unexpected_type("array of tables", item.type_name());
}
cx.pop_key();
@ -183,15 +214,11 @@ pub trait ReadExt<'a> {
if let Ok(value) = str.parse::<T>() {
Some(value)
} else {
cx.add_warning(format!("Invalid {}: unsupported value {str:?}", cx.path()));
cx.unsupported_value(str.to_owned());
None
}
} else {
cx.add_warning(format!(
"Invalid {}: expected string but found {}",
cx.path(),
item.type_name()
));
cx.unexpected_type("string", item.type_name());
None
}
} else {
@ -210,11 +237,7 @@ pub trait ReadExt<'a> {
if let Some(value) = item.as_bool() {
Some(value)
} else {
cx.add_warning(format!(
"Invalid {}: expected boolean but found {}",
cx.path(),
item.type_name()
));
cx.unexpected_type("boolean", item.type_name());
None
}
} else {
@ -233,11 +256,7 @@ pub trait ReadExt<'a> {
if let Some(value) = item.as_float() {
Some(value)
} else {
cx.add_warning(format!(
"Invalid {}: expected float but found {}",
cx.path(),
item.type_name()
));
cx.unexpected_type("float", item.type_name());
None
}
} else {