use serde::Deserialize;
#[derive(Deserialize)]
pub(crate) struct Borrowed<'a> {
pub(crate) value: &'a str,
}
fn borrowed_from(input: &[u8], field: &str) -> bool {
let start = input.as_ptr();
let end = input.as_ptr().wrapping_add(input.len());
let at = field.as_ptr();
at >= start && at < end
}
fn assert_borrows_from_text_and_slice<ET, ES>(
codec: &str,
document: &str,
expected: &str,
decode_text: impl for<'a> FnOnce(&'a str) -> Result<Borrowed<'a>, ET>,
decode_slice: impl for<'a> FnOnce(&'a [u8]) -> Result<Borrowed<'a>, ES>,
) -> Result<(), String>
where
ET: std::fmt::Debug,
ES: std::fmt::Debug,
{
let from_text = decode_text(document)
.map_err(|error| format!("{codec}: text decode refused: {error:?}"))?;
assert_eq!(
from_text.value, expected,
"{codec}: the parsed text field retains its value"
);
assert!(
borrowed_from(document.as_bytes(), from_text.value),
"{codec}: an unescaped text field must borrow from the supplied text"
);
let from_slice = decode_slice(document.as_bytes())
.map_err(|error| format!("{codec}: slice decode refused: {error:?}"))?;
assert_eq!(
from_slice.value, expected,
"{codec}: the parsed slice field retains its value"
);
assert!(
borrowed_from(document.as_bytes(), from_slice.value),
"{codec}: an unescaped slice field must borrow from the supplied input"
);
Ok(())
}
fn assert_escaped_field_is_refused<ET>(
codec: &str,
escaped_document: &str,
decode_text: impl for<'a> FnOnce(&'a str) -> Result<Borrowed<'a>, ET>,
) {
assert!(
decode_text(escaped_document).is_err(),
"{codec}: an escaped field needs owned decoded storage, not a borrow"
);
}
#[cfg(feature = "json")]
pub(crate) fn json_borrow_contract() -> Result<(), String> {
assert_borrows_from_text_and_slice(
"json",
r#"{"value":"borrowed"}"#,
"borrowed",
|text| crate::json::from_str::<Borrowed<'_>>(text),
|bytes| crate::json::from_slice::<Borrowed<'_>>(bytes),
)
}
#[cfg(feature = "ron")]
pub(crate) fn ron_borrow_contract() -> Result<(), String> {
assert_borrows_from_text_and_slice(
"ron",
"(value: \"borrowed\")",
"borrowed",
|text| crate::ron::from_str::<Borrowed<'_>>(text),
|bytes| crate::ron::from_slice::<Borrowed<'_>>(bytes),
)
}
#[cfg(feature = "json")]
pub(crate) fn json_escaped_field_is_refused() {
assert_escaped_field_is_refused("json", r#"{"value":"line\nfeed"}"#, |text| {
crate::json::from_str::<Borrowed<'_>>(text)
});
}
#[cfg(feature = "ron")]
pub(crate) fn ron_escaped_field_is_refused() {
assert_escaped_field_is_refused("ron", "(value: \"line\\nfeed\")", |text| {
crate::ron::from_str::<Borrowed<'_>>(text)
});
}