use toml_edit::{value, DocumentMut, Item, Table, Value};
use crate::error::{Malformed, Result};
use crate::{PAYLOAD_FILE_KEY, VERSION_KEY};
pub(crate) struct Keys {
pub version: String,
pub payload_file: String,
}
pub(crate) fn parse(bytes: &[u8]) -> Result<(DocumentMut, Keys)> {
let doc = document(bytes)?;
let keys = Keys {
version: required_string(&doc, VERSION_KEY)?.to_owned(),
payload_file: required_string(&doc, PAYLOAD_FILE_KEY)?.to_owned(),
};
Ok((doc, keys))
}
pub(crate) fn document(bytes: &[u8]) -> Result<DocumentMut> {
let text = std::str::from_utf8(bytes).map_err(|_| Malformed::MetadataNotUtf8)?;
text.parse()
.map_err(|e: toml_edit::TomlError| Malformed::MetadataNotToml(e.to_string()).into())
}
pub(crate) fn lookup<'d>(doc: &'d DocumentMut, key: &str) -> Option<&'d Item> {
key.split('.')
.try_fold(doc.as_item(), |item, part| item.get(part))
}
pub(crate) fn required_string<'d>(doc: &'d DocumentMut, key: &'static str) -> Result<&'d str> {
lookup(doc, key)
.ok_or(Malformed::MissingKey(key))?
.as_str()
.ok_or_else(|| Malformed::KeyNotAString(key).into())
}
pub(crate) fn set(doc: &mut DocumentMut, key: &str, to: &str) -> Result<()> {
let path: Vec<&str> = key.split('.').collect();
let (last, tables) = path.split_last().expect("a key is never empty");
let mut at = doc.as_item_mut();
for part in tables {
match at.get(part) {
None => at[*part] = Item::Table(Table::new()),
Some(item) if item.is_table_like() => {}
Some(_) => return Err(Malformed::PayloadNotATable.into()),
}
at = &mut at[*part];
}
match at.get_mut(*last).and_then(Item::as_value_mut) {
Some(slot) => {
if slot.as_str() == Some(to) {
return Ok(());
}
let decor = slot.decor().clone();
*slot = Value::from(to);
*slot.decor_mut() = decor;
}
None => at[*last] = value(to),
}
Ok(())
}