#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Default)]
pub struct Body(String);
impl serde::Serialize for Body {
fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
s.serialize_str(&self.0)
}
}
impl Body {
pub fn new(s: impl Into<String>) -> Self {
Body(s.into().trim_end().to_string())
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl std::fmt::Display for Body {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
impl From<String> for Body {
fn from(s: String) -> Self {
Body::new(s)
}
}
impl From<&str> for Body {
fn from(s: &str) -> Self {
Body::new(s)
}
}
#[cfg(test)]
pub mod strategy {
use super::*;
use proptest::prelude::*;
pub fn arb_body() -> impl Strategy<Value = Body> {
".*".prop_map(Body::new)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_accepts_empty_string() {
let b = Body::new("");
assert_eq!(b.as_str(), "");
}
#[test]
fn new_accepts_plain_text() {
let b = Body::new("## Context\n\nWe need Rust.");
assert_eq!(b.as_str(), "## Context\n\nWe need Rust.");
}
#[test]
fn new_trims_trailing_newline() {
let b = Body::new("some content\n");
assert_eq!(b.as_str(), "some content");
}
#[test]
fn new_trims_multiple_trailing_newlines() {
let b = Body::new("some content\n\n\n");
assert_eq!(b.as_str(), "some content");
}
#[test]
fn new_trims_trailing_spaces() {
let b = Body::new("content ");
assert_eq!(b.as_str(), "content");
}
#[test]
fn new_preserves_leading_whitespace() {
let b = Body::new(" indented");
assert_eq!(b.as_str(), " indented");
}
#[test]
fn new_preserves_internal_newlines() {
let b = Body::new("line1\n\nline2\n");
assert_eq!(b.as_str(), "line1\n\nline2");
}
#[test]
fn display_roundtrips() {
let b = Body::new("## Context");
assert_eq!(b.to_string(), "## Context");
}
#[test]
fn equality_holds_for_same_value() {
assert_eq!(Body::new("abc"), Body::new("abc"));
}
#[test]
fn equality_normalises_trailing_newline() {
assert_eq!(Body::new("abc\n"), Body::new("abc"));
}
#[test]
fn from_str_roundtrips() {
let b: Body = "hello".into();
assert_eq!(b.as_str(), "hello");
}
#[test]
fn from_string_roundtrips() {
let b: Body = "hello".to_string().into();
assert_eq!(b.as_str(), "hello");
}
#[test]
fn default_is_empty() {
assert_eq!(Body::default().as_str(), "");
}
proptest::proptest! {
#[test]
fn prop_trim_end_is_idempotent(s in ".*") {
let b = Body::new(&s);
assert_eq!(Body::new(b.as_str()), b);
}
#[test]
fn prop_display_roundtrips(s in ".*") {
let b = Body::new(&s);
assert_eq!(b.to_string(), b.as_str());
}
#[test]
fn prop_trailing_newline_is_normalised(s in ".*") {
let with_nl = format!("{s}\n");
assert_eq!(Body::new(&s), Body::new(&with_nl));
}
}
}