frontend-utils: Made ParseResult return a DocumentHolder directly

This commit is contained in:
Nathan Adams 2024-04-04 02:07:39 +02:00
parent 2bd28c9c1c
commit 2b4cec664a
4 changed files with 284 additions and 335 deletions

View File

@ -48,12 +48,12 @@ impl GlobalPreferences {
let preferences = if preferences_path.exists() { let preferences = if preferences_path.exists() {
let contents = std::fs::read_to_string(&preferences_path) let contents = std::fs::read_to_string(&preferences_path)
.context("Failed to read saved preferences")?; .context("Failed to read saved preferences")?;
let (result, document) = read_preferences(&contents); let result = read_preferences(&contents);
for warning in result.warnings { for warning in result.warnings {
// TODO: A way to display warnings to users, generally // TODO: A way to display warnings to users, generally
tracing::warn!("{warning}"); tracing::warn!("{warning}");
} }
DocumentHolder::new(result.result, document) result.result
} else { } else {
Default::default() Default::default()
}; };
@ -62,11 +62,11 @@ impl GlobalPreferences {
let bookmarks = if bookmarks_path.exists() { let bookmarks = if bookmarks_path.exists() {
let contents = std::fs::read_to_string(&bookmarks_path) let contents = std::fs::read_to_string(&bookmarks_path)
.context("Failed to read saved bookmarks")?; .context("Failed to read saved bookmarks")?;
let (result, document) = read_bookmarks(&contents); let result = read_bookmarks(&contents);
for warning in result.warnings { for warning in result.warnings {
tracing::warn!("{warning}"); tracing::warn!("{warning}");
} }
DocumentHolder::new(result.result, document) result.result
} else { } else {
Default::default() Default::default()
}; };

View File

@ -1,5 +1,5 @@
use crate::preferences::{Bookmark, SavedGlobalPreferences}; use crate::preferences::{Bookmark, SavedGlobalPreferences};
use ruffle_frontend_utils::parse::{ParseContext, ParseResult, ReadExt}; use ruffle_frontend_utils::parse::{DocumentHolder, ParseContext, ParseResult, ReadExt};
use std::str::FromStr; use std::str::FromStr;
use toml_edit::DocumentMut; use toml_edit::DocumentMut;
@ -12,74 +12,74 @@ use toml_edit::DocumentMut;
/// Default values are used wherever an unknown or invalid value is found; /// Default values are used wherever an unknown or invalid value is found;
/// this is to support the case of, for example, a later version having different supported /// this is to support the case of, for example, a later version having different supported
/// backends than an older version. /// backends than an older version.
pub fn read_preferences(input: &str) -> (ParseResult<SavedGlobalPreferences>, DocumentMut) { pub fn read_preferences(input: &str) -> ParseResult<SavedGlobalPreferences> {
let mut result = ParseResult {
result: Default::default(),
warnings: vec![],
};
let document = match input.parse::<DocumentMut>() { let document = match input.parse::<DocumentMut>() {
Ok(document) => document, Ok(document) => document,
Err(e) => { Err(e) => {
result.add_warning(format!("Invalid TOML: {e}")); return ParseResult {
return (result, DocumentMut::default()); result: Default::default(),
warnings: vec![format!("Invalid TOML: {e}")],
}
} }
}; };
let mut result = SavedGlobalPreferences::default();
let mut cx = ParseContext::default(); let mut cx = ParseContext::default();
if let Some(value) = document.parse_from_str(&mut cx, "graphics_backend") { if let Some(value) = document.parse_from_str(&mut cx, "graphics_backend") {
result.result.graphics_backend = value; result.graphics_backend = value;
}; };
if let Some(value) = document.parse_from_str(&mut cx, "graphics_power_preference") { if let Some(value) = document.parse_from_str(&mut cx, "graphics_power_preference") {
result.result.graphics_power_preference = value; result.graphics_power_preference = value;
}; };
if let Some(value) = document.parse_from_str(&mut cx, "language") { if let Some(value) = document.parse_from_str(&mut cx, "language") {
result.result.language = value; result.language = value;
}; };
if let Some(value) = document.parse_from_str(&mut cx, "output_device") { if let Some(value) = document.parse_from_str(&mut cx, "output_device") {
result.result.output_device = Some(value); result.output_device = Some(value);
}; };
if let Some(value) = document.get_float(&mut cx, "volume") { if let Some(value) = document.get_float(&mut cx, "volume") {
result.result.volume = value.clamp(0.0, 1.0) as f32; result.volume = value.clamp(0.0, 1.0) as f32;
}; };
if let Some(value) = document.get_bool(&mut cx, "mute") { if let Some(value) = document.get_bool(&mut cx, "mute") {
result.result.mute = value; result.mute = value;
}; };
document.get_table_like(&mut cx, "log", |cx, log| { document.get_table_like(&mut cx, "log", |cx, log| {
if let Some(value) = log.parse_from_str(cx, "filename_pattern") { if let Some(value) = log.parse_from_str(cx, "filename_pattern") {
result.result.log.filename_pattern = value; result.log.filename_pattern = value;
}; };
}); });
document.get_table_like(&mut cx, "storage", |cx, storage| { document.get_table_like(&mut cx, "storage", |cx, storage| {
if let Some(value) = storage.parse_from_str(cx, "backend") { if let Some(value) = storage.parse_from_str(cx, "backend") {
result.result.storage.backend = value; result.storage.backend = value;
} }
}); });
result.warnings = cx.warnings; ParseResult {
(result, document) result: DocumentHolder::new(result, document),
warnings: cx.warnings,
}
} }
pub fn read_bookmarks(input: &str) -> (ParseResult<Vec<Bookmark>>, DocumentMut) { pub fn read_bookmarks(input: &str) -> ParseResult<Vec<Bookmark>> {
let mut result = ParseResult {
result: Default::default(),
warnings: vec![],
};
let document = match input.parse::<DocumentMut>() { let document = match input.parse::<DocumentMut>() {
Ok(document) => document, Ok(document) => document,
Err(e) => { Err(e) => {
result.add_warning(format!("Invalid TOML: {e}")); return ParseResult {
return (result, DocumentMut::default()); result: Default::default(),
warnings: vec![format!("Invalid TOML: {e}")],
}
} }
}; };
let mut result = Vec::new();
let mut cx = ParseContext::default(); let mut cx = ParseContext::default();
document.get_array_of_tables(&mut cx, "bookmark", |cx, bookmarks| { document.get_array_of_tables(&mut cx, "bookmark", |cx, bookmarks| {
@ -96,12 +96,14 @@ pub fn read_bookmarks(input: &str) -> (ParseResult<Vec<Bookmark>>, DocumentMut)
None => crate::util::url_to_readable_name(&url).into_owned(), None => crate::util::url_to_readable_name(&url).into_owned(),
}; };
result.result.push(Bookmark { url, name }); result.push(Bookmark { url, name });
} }
}); });
result.warnings = cx.warnings; ParseResult {
(result, document) result: DocumentHolder::new(result, document),
warnings: cx.warnings,
}
} }
#[allow(clippy::unwrap_used)] #[allow(clippy::unwrap_used)]
@ -116,473 +118,381 @@ mod tests {
#[test] #[test]
fn invalid_toml() { fn invalid_toml() {
let result = read_preferences("~~INVALID~~").0; let result = read_preferences("~~INVALID~~");
assert_eq!(ParseResult{result: Default::default(), warnings: assert_eq!(&SavedGlobalPreferences::default(), result.values());
vec![ assert_eq!(vec!["Invalid TOML: TOML parse error at line 1, column 1\n |\n1 | ~~INVALID~~\n | ^\ninvalid key\n".to_string()], result.warnings);
"Invalid TOML: TOML parse error at line 1, column 1\n |\n1 | ~~INVALID~~\n | ^\ninvalid key\n".to_string()
]}, result
);
} }
#[test] #[test]
fn empty_toml() { fn empty_toml() {
let result = read_preferences("").0; let result = read_preferences("");
assert_eq!( assert_eq!(&SavedGlobalPreferences::default(), result.values());
ParseResult { assert_eq!(Vec::<String>::new(), result.warnings);
result: Default::default(),
warnings: vec![]
},
result
);
} }
#[test] #[test]
fn invalid_backend_type() { fn invalid_backend_type() {
let result = read_preferences("graphics_backend = 5").0; let result = read_preferences("graphics_backend = 5");
assert_eq!(&SavedGlobalPreferences::default(), result.values());
assert_eq!( assert_eq!(
ParseResult { vec!["Invalid graphics_backend: expected string but found integer".to_string()],
result: Default::default(), result.warnings
warnings: vec![
"Invalid graphics_backend: expected string but found integer".to_string()
]
},
result
); );
} }
#[test] #[test]
fn invalid_backend_value() { fn invalid_backend_value() {
let result = read_preferences("graphics_backend = \"fast\"").0; let result = read_preferences("graphics_backend = \"fast\"");
assert_eq!(&SavedGlobalPreferences::default(), result.values());
assert_eq!( assert_eq!(
ParseResult { vec!["Invalid graphics_backend: unsupported value \"fast\"".to_string()],
result: Default::default(), result.warnings
warnings: vec!["Invalid graphics_backend: unsupported value \"fast\"".to_string()]
},
result
); );
} }
#[test] #[test]
fn correct_backend_value() { fn correct_backend_value() {
let result = read_preferences("graphics_backend = \"vulkan\"").0; let result = read_preferences("graphics_backend = \"vulkan\"");
assert_eq!( assert_eq!(
ParseResult { &SavedGlobalPreferences {
result: SavedGlobalPreferences { graphics_backend: GraphicsBackend::Vulkan,
graphics_backend: GraphicsBackend::Vulkan, ..Default::default()
..Default::default()
},
warnings: vec![]
}, },
result result.values()
); );
assert_eq!(Vec::<String>::new(), result.warnings);
} }
#[test] #[test]
fn invalid_power_type() { fn invalid_power_type() {
let result = read_preferences("graphics_power_preference = 5").0; let result = read_preferences("graphics_power_preference = 5");
assert_eq!(&SavedGlobalPreferences::default(), result.values());
assert_eq!( assert_eq!(
ParseResult { vec![
result: Default::default(), "Invalid graphics_power_preference: expected string but found integer".to_string()
warnings: vec![ ],
"Invalid graphics_power_preference: expected string but found integer" result.warnings
.to_string()
]
},
result
); );
} }
#[test] #[test]
fn invalid_power_value() { fn invalid_power_value() {
let result = read_preferences("graphics_power_preference = \"fast\"").0; let result = read_preferences("graphics_power_preference = \"fast\"");
assert_eq!(&SavedGlobalPreferences::default(), result.values());
assert_eq!( assert_eq!(
ParseResult { vec!["Invalid graphics_power_preference: unsupported value \"fast\"".to_string()],
result: Default::default(), result.warnings
warnings: vec![
"Invalid graphics_power_preference: unsupported value \"fast\"".to_string()
]
},
result
); );
} }
#[test] #[test]
fn correct_power_value() { fn correct_power_value() {
let result = read_preferences("graphics_power_preference = \"low\"").0; let result = read_preferences("graphics_power_preference = \"low\"");
assert_eq!( assert_eq!(
ParseResult { &SavedGlobalPreferences {
result: SavedGlobalPreferences { graphics_power_preference: PowerPreference::Low,
graphics_power_preference: PowerPreference::Low, ..Default::default()
..Default::default()
},
warnings: vec![]
}, },
result result.values()
); );
assert_eq!(Vec::<String>::new(), result.warnings);
} }
#[test] #[test]
fn invalid_language_value() { fn invalid_language_value() {
let result = read_preferences("language = \"???\"").0; let result = read_preferences("language = \"???\"");
assert_eq!(&SavedGlobalPreferences::default(), result.values());
assert_eq!( assert_eq!(
ParseResult { vec!["Invalid language: unsupported value \"???\"".to_string()],
result: Default::default(), result.warnings
warnings: vec!["Invalid language: unsupported value \"???\"".to_string()]
},
result
); );
} }
#[test] #[test]
fn correct_language_value() { fn correct_language_value() {
let result = read_preferences("language = \"en-US\"").0; let result = read_preferences("language = \"en-US\"");
assert_eq!( assert_eq!(
ParseResult { &SavedGlobalPreferences {
result: SavedGlobalPreferences { language: langid!("en-US"),
language: langid!("en-US"), ..Default::default()
..Default::default()
},
warnings: vec![]
}, },
result result.values()
); );
assert_eq!(Vec::<String>::new(), result.warnings);
} }
#[test] #[test]
fn correct_output_device() { fn correct_output_device() {
let result = read_preferences("output_device = \"Speakers\"").0; let result = read_preferences("output_device = \"Speakers\"");
assert_eq!( assert_eq!(
ParseResult { &SavedGlobalPreferences {
result: SavedGlobalPreferences { output_device: Some("Speakers".to_string()),
output_device: Some("Speakers".to_string()), ..Default::default()
..Default::default()
},
warnings: vec![]
}, },
result result.values()
); );
assert_eq!(Vec::<String>::new(), result.warnings);
} }
#[test] #[test]
fn invalid_output_device() { fn invalid_output_device() {
let result = read_preferences("output_device = 5").0; let result = read_preferences("output_device = 5");
assert_eq!( assert_eq!(
ParseResult { &SavedGlobalPreferences {
result: SavedGlobalPreferences { output_device: None,
output_device: None, ..Default::default()
..Default::default()
},
warnings: vec![
"Invalid output_device: expected string but found integer".to_string()
]
}, },
result result.values()
);
assert_eq!(
vec!["Invalid output_device: expected string but found integer".to_string()],
result.warnings
); );
} }
#[test] #[test]
fn mute() { fn mute() {
let result = read_preferences("mute = \"false\"");
assert_eq!( assert_eq!(
ParseResult { &SavedGlobalPreferences {
result: SavedGlobalPreferences { mute: false,
mute: false, ..Default::default()
..Default::default()
},
warnings: vec!["Invalid mute: expected boolean but found string".to_string()]
}, },
read_preferences("mute = \"false\"").0 result.values()
);
assert_eq!(
vec!["Invalid mute: expected boolean but found string".to_string()],
result.warnings
); );
let result = read_preferences("mute = true");
assert_eq!( assert_eq!(
ParseResult { &SavedGlobalPreferences {
result: SavedGlobalPreferences { mute: true,
mute: true, ..Default::default()
..Default::default()
},
warnings: vec![]
}, },
read_preferences("mute = true").0 result.values()
); );
assert_eq!(Vec::<String>::new(), result.warnings);
let result = read_preferences("");
assert_eq!( assert_eq!(
ParseResult { &SavedGlobalPreferences {
result: SavedGlobalPreferences { mute: false,
mute: false, ..Default::default()
..Default::default()
},
warnings: vec![]
}, },
read_preferences("").0 result.values()
); );
assert_eq!(Vec::<String>::new(), result.warnings);
} }
#[test] #[test]
fn volume() { fn volume() {
let result = read_preferences("volume = \"0.5\"");
assert_eq!( assert_eq!(
ParseResult { &SavedGlobalPreferences {
result: SavedGlobalPreferences { volume: 1.0,
volume: 1.0, ..Default::default()
..Default::default()
},
warnings: vec!["Invalid volume: expected float but found string".to_string()]
}, },
read_preferences("volume = \"0.5\"").0 result.values()
);
assert_eq!(
vec!["Invalid volume: expected float but found string".to_string()],
result.warnings
); );
let result = read_preferences("volume = 0.5");
assert_eq!( assert_eq!(
ParseResult { &SavedGlobalPreferences {
result: SavedGlobalPreferences { volume: 0.5,
volume: 0.5, ..Default::default()
..Default::default()
},
warnings: vec![]
}, },
read_preferences("volume = 0.5").0 result.values()
); );
assert_eq!(Vec::<String>::new(), result.warnings);
let result = read_preferences("volume = -1.0");
assert_eq!( assert_eq!(
ParseResult { &SavedGlobalPreferences {
result: SavedGlobalPreferences { volume: 0.0,
volume: 0.0, ..Default::default()
..Default::default()
},
warnings: vec![]
}, },
read_preferences("volume = -1.0").0 result.values()
); );
assert_eq!(Vec::<String>::new(), result.warnings);
} }
#[test] #[test]
fn log_filename() { fn log_filename() {
let result = read_preferences("log = {filename_pattern = 5}");
assert_eq!( assert_eq!(
ParseResult { &SavedGlobalPreferences {
result: SavedGlobalPreferences { log: LogPreferences {
log: LogPreferences {
..Default::default()
},
..Default::default() ..Default::default()
}, },
warnings: vec![ ..Default::default()
"Invalid log.filename_pattern: expected string but found integer".to_string()
]
}, },
read_preferences("log = {filename_pattern = 5}").0 result.values()
);
assert_eq!(
vec!["Invalid log.filename_pattern: expected string but found integer".to_string()],
result.warnings
); );
let result = read_preferences("log = {filename_pattern = \"???\"}");
assert_eq!(&SavedGlobalPreferences::default(), result.values());
assert_eq!( assert_eq!(
ParseResult { vec!["Invalid log.filename_pattern: unsupported value \"???\"".to_string()],
result: SavedGlobalPreferences { result.warnings
log: LogPreferences {
..Default::default()
},
..Default::default()
},
warnings: vec![
"Invalid log.filename_pattern: unsupported value \"???\"".to_string()
]
},
read_preferences("log = {filename_pattern = \"???\"}").0
); );
let result = read_preferences("log = {filename_pattern = \"with_timestamp\"}");
assert_eq!( assert_eq!(
ParseResult { &SavedGlobalPreferences {
result: SavedGlobalPreferences { log: LogPreferences {
log: LogPreferences { filename_pattern: FilenamePattern::WithTimestamp,
filename_pattern: FilenamePattern::WithTimestamp,
},
..Default::default()
}, },
warnings: vec![] ..Default::default()
}, },
read_preferences("log = {filename_pattern = \"with_timestamp\"}").0 result.values()
); );
assert_eq!(Vec::<String>::new(), result.warnings);
} }
#[test] #[test]
fn log() { fn log() {
let result = read_preferences("log = \"yes\"");
assert_eq!(&SavedGlobalPreferences::default(), result.values());
assert_eq!( assert_eq!(
ParseResult { vec!["Invalid log: expected table but found string".to_string()],
result: SavedGlobalPreferences { result.warnings
..Default::default()
},
warnings: vec!["Invalid log: expected table but found string".to_string()]
},
read_preferences("log = \"yes\"").0
); );
} }
#[test] #[test]
fn storage_backend() { fn storage_backend() {
let result = read_preferences("storage = {backend = 5}");
assert_eq!(&SavedGlobalPreferences::default(), result.values());
assert_eq!( assert_eq!(
ParseResult { vec!["Invalid storage.backend: expected string but found integer".to_string()],
result: SavedGlobalPreferences { result.warnings
storage: StoragePreferences {
..Default::default()
},
..Default::default()
},
warnings: vec![
"Invalid storage.backend: expected string but found integer".to_string()
]
},
read_preferences("storage = {backend = 5}").0
); );
let result = read_preferences("storage = {backend = \"???\"}");
assert_eq!(&SavedGlobalPreferences::default(), result.values());
assert_eq!( assert_eq!(
ParseResult { vec!["Invalid storage.backend: unsupported value \"???\"".to_string()],
result: SavedGlobalPreferences { result.warnings
storage: StoragePreferences {
..Default::default()
},
..Default::default()
},
warnings: vec!["Invalid storage.backend: unsupported value \"???\"".to_string()]
},
read_preferences("storage = {backend = \"???\"}").0
); );
let result = read_preferences("storage = {backend = \"memory\"}");
assert_eq!( assert_eq!(
ParseResult { &SavedGlobalPreferences {
result: SavedGlobalPreferences { storage: StoragePreferences {
storage: StoragePreferences { backend: StorageBackend::Memory,
backend: StorageBackend::Memory,
},
..Default::default()
}, },
warnings: vec![] ..Default::default()
}, },
read_preferences("storage = {backend = \"memory\"}").0 result.values()
); );
assert_eq!(Vec::<String>::new(), result.warnings);
} }
#[test] #[test]
fn storage() { fn storage() {
let result = read_preferences("storage = \"no\"");
assert_eq!(&SavedGlobalPreferences::default(), result.values());
assert_eq!( assert_eq!(
ParseResult { vec!["Invalid storage: expected table but found string".to_string()],
result: SavedGlobalPreferences { result.warnings
..Default::default()
},
warnings: vec!["Invalid storage: expected table but found string".to_string()]
},
read_preferences("storage = \"no\"").0
); );
} }
#[test] #[test]
fn bookmark() { fn bookmark() {
let result = read_bookmarks("[bookmark]");
assert_eq!(&Vec::<Bookmark>::new(), result.values());
assert_eq!( assert_eq!(
ParseResult { vec!["Invalid bookmark: expected array of tables but found table".to_string()],
result: vec![], result.warnings
warnings: vec![
"Invalid bookmark: expected array of tables but found table".to_string()
]
},
read_bookmarks("[bookmark]").0
); );
let result = read_bookmarks("[[bookmark]]");
assert_eq!( assert_eq!(
ParseResult { &vec![Bookmark {
result: vec![Bookmark { url: Url::parse(crate::preferences::INVALID_URL).unwrap(),
url: Url::parse(crate::preferences::INVALID_URL).unwrap(), name: "".to_string(),
name: "".to_string(), }],
}], result.values()
warnings: vec![], );
}, assert_eq!(Vec::<String>::new(), result.warnings);
read_bookmarks("[[bookmark]]").0
let result = read_bookmarks("[[bookmark]]\nurl = \"invalid\"");
assert_eq!(
&vec![Bookmark {
url: Url::parse(crate::preferences::INVALID_URL).unwrap(),
name: "".to_string(),
}],
result.values()
);
assert_eq!(
vec!["Invalid bookmark.url: unsupported value \"invalid\"".to_string()],
result.warnings
); );
assert_eq!( let result = read_bookmarks(
ParseResult { "[[bookmark]]\nurl = \"https://ruffle.rs/logo-anim.swf\"\nname = \"Logo SWF\"",
result: vec![Bookmark {
url: Url::parse(crate::preferences::INVALID_URL).unwrap(),
name: "".to_string(),
}],
warnings: vec!["Invalid bookmark.url: unsupported value \"invalid\"".to_string()],
},
read_bookmarks("[[bookmark]]\nurl = \"invalid\"").0,
); );
assert_eq!( assert_eq!(
ParseResult { &vec![Bookmark {
result: vec![Bookmark { url: Url::parse("https://ruffle.rs/logo-anim.swf").unwrap(),
url: Url::parse("https://ruffle.rs/logo-anim.swf").unwrap(), name: "Logo SWF".to_string(),
name: "Logo SWF".to_string(), }],
}], result.values()
warnings: vec![],
},
read_bookmarks(
"[[bookmark]]\nurl = \"https://ruffle.rs/logo-anim.swf\"\nname = \"Logo SWF\""
)
.0
); );
assert_eq!(Vec::<String>::new(), result.warnings);
} }
#[test] #[test]
fn multiple_bookmarks() { fn multiple_bookmarks() {
assert_eq!( let result = read_bookmarks(
ParseResult { r#"
result: vec![
Bookmark {
url: Url::from_str("file:///home/user/example.swf").unwrap(),
name: "example.swf".to_string(),
},
Bookmark {
url: Url::from_str("https://ruffle.rs/logo-anim.swf").unwrap(),
name: "logo-anim.swf".to_string(),
}
],
warnings: vec![],
},
read_bookmarks(
r#"
[[bookmark]] [[bookmark]]
url = "file:///home/user/example.swf" url = "file:///home/user/example.swf"
[[bookmark]] [[bookmark]]
url = "https://ruffle.rs/logo-anim.swf" url = "https://ruffle.rs/logo-anim.swf"
"# "#,
)
.0
); );
assert_eq!( assert_eq!(
ParseResult { &vec![
result: vec![ Bookmark {
Bookmark { url: Url::from_str("file:///home/user/example.swf").unwrap(),
url: Url::from_str("file:///home/user/example.swf").unwrap(), name: "example.swf".to_string(),
name: "example.swf".to_string(), },
}, Bookmark {
Bookmark { url: Url::from_str("https://ruffle.rs/logo-anim.swf").unwrap(),
url: Url::parse(crate::preferences::INVALID_URL).unwrap(), name: "logo-anim.swf".to_string(),
name: "".to_string(), }
}, ],
Bookmark { result.values()
url: Url::parse(crate::preferences::INVALID_URL).unwrap(), );
name: "".to_string(), assert_eq!(Vec::<String>::new(), result.warnings);
},
Bookmark {
url: Url::from_str("https://ruffle.rs/logo-anim.swf").unwrap(),
name: "logo-anim.swf".to_string(),
}
],
warnings: vec!["Invalid bookmark.url: unsupported value \"invalid\"".to_string(),], let result = read_bookmarks(
}, r#"
read_bookmarks(
r#"
[[bookmark]] [[bookmark]]
url = "file:///home/user/example.swf" url = "file:///home/user/example.swf"
@ -593,9 +503,32 @@ mod tests {
[[bookmark]] [[bookmark]]
url = "https://ruffle.rs/logo-anim.swf" url = "https://ruffle.rs/logo-anim.swf"
"# "#,
) );
.0 assert_eq!(
&vec![
Bookmark {
url: Url::from_str("file:///home/user/example.swf").unwrap(),
name: "example.swf".to_string(),
},
Bookmark {
url: Url::parse(crate::preferences::INVALID_URL).unwrap(),
name: "".to_string(),
},
Bookmark {
url: Url::parse(crate::preferences::INVALID_URL).unwrap(),
name: "".to_string(),
},
Bookmark {
url: Url::from_str("https://ruffle.rs/logo-anim.swf").unwrap(),
name: "logo-anim.swf".to_string(),
}
],
result.values()
);
assert_eq!(
vec!["Invalid bookmark.url: unsupported value \"invalid\"".to_string()],
result.warnings
); );
} }
} }

View File

@ -155,22 +155,17 @@ mod tests {
macro_rules! define_serialization_test_helpers { macro_rules! define_serialization_test_helpers {
($read_method:ident, $doc_struct:ty, $writer:ident) => { ($read_method:ident, $doc_struct:ty, $writer:ident) => {
fn parse(input: &str) -> DocumentHolder<$doc_struct> {
let (result, document) = $read_method(input);
DocumentHolder::new(result.result, document)
}
fn check_roundtrip(preferences: &DocumentHolder<$doc_struct>) { fn check_roundtrip(preferences: &DocumentHolder<$doc_struct>) {
let read_result = $read_method(&preferences.serialize()); let read_result = $read_method(&preferences.serialize());
assert_eq!( assert_eq!(
*preferences.deref(), preferences.deref(),
read_result.0.result, read_result.values(),
"roundtrip failed: expected != actual" "roundtrip failed: expected != actual"
); );
} }
fn test(original: &str, fun: impl FnOnce(&mut $writer), expected: &str) { fn test(original: &str, fun: impl FnOnce(&mut $writer), expected: &str) {
let mut preferences = parse(original); let mut preferences = $read_method(original).result;
let mut writer = $writer::new(&mut preferences); let mut writer = $writer::new(&mut preferences);
fun(&mut writer); fun(&mut writer);
check_roundtrip(&preferences); check_roundtrip(&preferences);

View File

@ -1,4 +1,5 @@
use std::fmt; use std::fmt;
use std::fmt::Formatter;
use std::ops::Deref; use std::ops::Deref;
use std::str::FromStr; use std::str::FromStr;
use toml_edit::{ArrayOfTables, DocumentMut, Item, Table, TableLike}; use toml_edit::{ArrayOfTables, DocumentMut, Item, Table, TableLike};
@ -36,6 +37,14 @@ impl<T: Default> Default for DocumentHolder<T> {
} }
} }
impl<T: fmt::Debug> fmt::Debug for DocumentHolder<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("DocumentHolder")
.field("inner", &self.inner)
.finish()
}
}
impl<T> DocumentHolder<T> { impl<T> DocumentHolder<T> {
pub fn new(values: T, document: DocumentMut) -> Self { pub fn new(values: T, document: DocumentMut) -> Self {
Self { Self {
@ -64,16 +73,28 @@ impl<T> DocumentHolder<T> {
} }
} }
#[derive(Debug, PartialEq)] pub struct ParseResult<T> {
pub struct ParseResult<T: PartialEq + fmt::Debug> { pub result: DocumentHolder<T>,
pub result: T,
pub warnings: Vec<String>, pub warnings: Vec<String>,
} }
impl<T: fmt::Debug + PartialEq> ParseResult<T> { impl<T: fmt::Debug> fmt::Debug for ParseResult<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("ParseResult")
.field("result", &self.result)
.field("warnings", &self.warnings)
.finish()
}
}
impl<T> ParseResult<T> {
pub fn add_warning(&mut self, message: String) { pub fn add_warning(&mut self, message: String) {
self.warnings.push(message); self.warnings.push(message);
} }
pub fn values(&self) -> &T {
&self.result
}
} }
#[derive(Default)] #[derive(Default)]