Skip to content

Commit

Permalink
feat(Renderable) Add a new Value variant Renderable that supports…
Browse files Browse the repository at this point in the history
… `Display` and `Debug` values
  • Loading branch information
johnhurt committed Sep 22, 2024
1 parent 6d67668 commit 989f83b
Show file tree
Hide file tree
Showing 6 changed files with 391 additions and 1 deletion.
55 changes: 55 additions & 0 deletions tests/tests/renderable.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
use valuable::{Renderable, Valuable};

#[derive(Debug)]
struct NotTuplable<'a> {
s: &'a str,
i: usize,
}

impl<'a> core::fmt::Display for NotTuplable<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"The string is \"{}\", and the integer is {}.",
self.s, self.i
)
}
}

#[test]
fn test_renderable_struct_from_debug() {
let s_owned = "Hello, Valuable World".to_owned();
let s = NotTuplable { s: &s_owned, i: 42 };

let r = Renderable::Debug(&s);
let v = r.as_value();

// Rendering should produce the debug output for the struct
assert_eq!(r.render_to_string(), format!("{s:?}"));

// Writing the value itself as `Debug` should print the same debug output
// as the struct
assert_eq!(format!("{v:?}"), format!("{s:?}"));
assert_eq!(format!("{v:#?}"), format!("{s:#?}"));
}

#[test]
fn test_renderable_struct_from_display() {
let s_owned = "Hello, Valuable World".to_owned();
let s = NotTuplable { s: &s_owned, i: 42 };

let r = Renderable::Display(&s);
let v = r.as_value();

// Rendering should produce the display output for the struct
assert_eq!(r.render_to_string(), format!("{s}"));

// Just to make sure, the display output should be different for the debug
// output
assert_ne!(r.render_to_string(), format!("{s:?}"));

// Writing the value itself as `Debug` should print the same display output
// as the struct
assert_eq!(format!("{v:?}"), format!("{s}"));
assert_eq!(format!("{v:#?}"), format!("{s:#}"));
}
11 changes: 11 additions & 0 deletions valuable-serde/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,17 @@ where
Value::Path(p) => Serialize::serialize(p, serializer),
#[cfg(feature = "std")]
Value::Error(e) => SerializeError(e).serialize(serializer),
Value::Renderable(r) => {
#[cfg(feature = "alloc")]
{
serializer.serialize_str(&r.render_to_string())
}
#[cfg(not(feature = "alloc"))]
{
// Can't serialize a renderable without allocating
Ok(S::Ok)
}
}

v => unimplemented!("{:?}", v),
}
Expand Down
3 changes: 3 additions & 0 deletions valuable/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,9 @@ pub use mappable::Mappable;
mod named_values;
pub use named_values::NamedValues;

mod renderable;
pub use renderable::Renderable;

mod slice;
pub use slice::Slice;

Expand Down
286 changes: 286 additions & 0 deletions valuable/src/renderable.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,286 @@
use core::fmt::{self, Debug, Display, Formatter, Write};

use crate::{Slice, Valuable, Value};

/// A [`Valuable`] sub-type for using ordinarily non-`valuable` types by
/// rendering them to a string with [`Debug`] or [`Display`].
///
/// This is most useful when defining a [`Structable`] value that includes
/// fields of types where [`Valuable`] cannot be implemented like types
/// contained in external crates.
///
/// ```
/// use valuable::{Valuable, Value, Visit, Renderable};
///
/// #[derive(Debug)]
/// struct NotValuable {
/// foo: u32,
/// bar: u32,
/// }
///
/// struct Render(String);
///
/// impl Visit for Render {
/// fn visit_value(&mut self, value: Value<'_>) {
/// let Value::Renderable(v) = value else { return };
/// self.0 = v.render_to_string();
/// }
/// }
///
/// let my_struct = NotValuable {
/// foo: 123,
/// bar: 456,
/// };
///
/// let mut renderer = Render(String::default());
///
/// // Render it plain
///
/// valuable::visit(&Renderable::Debug(&my_struct), &mut renderer);
/// assert_eq!(renderer.0, "NotValuable { foo: 123, bar: 456 }");
///
/// // Or render it pretty
/// assert_eq!(Renderable::Debug(&my_struct).render_to_string_with_prettiness(true),
/// "NotValuable {
/// foo: 123,
/// bar: 456,
/// }");
///
/// ```
#[derive(Clone, Copy)]
pub enum Renderable<'a> {
/// Renderable sub-type that is rendered via its [`Debug`] implementation
/// ```
/// use valuable::{Valuable, Value, Visit, Renderable};
///
/// #[derive(Debug)]
/// struct NotValuable {
/// foo: u32,
/// bar: u32,
/// }
///
/// struct Renderer(String);
///
/// impl Visit for Renderer {
/// fn visit_value(&mut self, value: Value<'_>) {
/// let Value::Renderable(v) = value else { return };
/// self.0 = v.render_to_string();
/// }
/// }
///
/// let my_struct = NotValuable {
/// foo: 123,
/// bar: 456,
/// };
///
/// let mut renderer = Renderer(String::default());
///
/// valuable::visit(&Renderable::Debug(&my_struct), &mut renderer);
/// assert_eq!(renderer.0, "NotValuable { foo: 123, bar: 456 }");
/// ```
Debug(
/// The actual type to be rendered with [`Debug::fmt`]
&'a dyn Debug,
),

/// Renderable sub-type that is rendered via its [`Display`] implementation
/// ```
/// use valuable::{Valuable, Value, Visit, Renderable};
/// use core::fmt;
///
/// struct NotValuable {
/// foo: u32,
/// bar: u32,
/// }
///
/// struct Renderer(String);
///
/// impl Visit for Renderer {
/// fn visit_value(&mut self, value: Value<'_>) {
/// let Value::Renderable(v) = value else { return };
/// self.0 = v.render_to_string();
/// }
/// }
///
/// let my_struct = NotValuable {
/// foo: 123,
/// bar: 456,
/// };
///
/// impl fmt::Display for NotValuable {
/// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
/// write!(f, "[Foo: {}, Bar: {}]", &self.foo, &self.bar)
/// }
/// }
///
/// let mut renderer = Renderer(String::default());
///
/// valuable::visit(
/// &Renderable::Display(&my_struct),
/// &mut renderer);
/// assert_eq!(renderer.0, "[Foo: 123, Bar: 456]");
/// ```
Display(
/// The actual type to be rendered with [`Display::fmt`]
&'a dyn Display,
),
}

impl<'a> Debug for Renderable<'a> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
Renderable::Debug(inner) => {
if f.alternate() {
write!(f, "{:#?}", inner)
} else {
write!(f, "{:?}", inner)
}
}
Renderable::Display(inner) => {
if f.alternate() {
write!(f, "{:#}", inner)
} else {
write!(f, "{}", inner)
}
}
}
}
}

impl<'a> Renderable<'a> {
/// Render this [`Renderable`] to the given [`Write`] target
/// ```
/// use valuable::{Valuable, Value, Visit, Renderable};
/// use core::fmt;
///
/// #[derive(Debug)]
/// struct NotValuable {
/// foo: u32,
/// bar: u32,
/// }
///
/// let my_struct = NotValuable {
/// foo: 123,
/// bar: 456,
/// };
///
/// let mut buf = String::new();
/// Renderable::Debug(&my_struct).render(&mut buf);
/// assert_eq!(buf, "NotValuable { foo: 123, bar: 456 }");
/// ```
#[inline]
pub fn render(&self, target: &mut dyn Write) -> fmt::Result {
write!(target, "{self:?}")
}

/// Render this [`Renderable`] to the given [`Write`] target, but force the
/// prettiness/alternate to be the given value
/// ```
/// use valuable::{Valuable, Value, Visit, Renderable};
/// use core::fmt;
///
/// #[derive(Debug)]
/// struct NotValuable {
/// foo: u32,
/// bar: u32,
/// }
///
/// let my_struct = NotValuable {
/// foo: 123,
/// bar: 456,
/// };
///
/// let mut buf = String::new();
/// Renderable::Debug(&my_struct).render_with_prettiness(&mut buf, true);
/// assert_eq!(buf,
/// "NotValuable {
/// foo: 123,
/// bar: 456,
/// }");
/// ```
#[inline]
pub fn render_with_prettiness(&self, target: &mut dyn Write, pretty: bool) -> fmt::Result {
if pretty {
write!(target, "{self:#?}")
} else {
write!(target, "{self:?}")
}
}

/// Render this [`Renderable`] to an owned [`String`]
/// ```
/// use valuable::{Valuable, Value, Visit, Renderable};
/// use core::fmt;
///
/// #[derive(Debug)]
/// struct NotValuable {
/// foo: u32,
/// bar: u32,
/// }
///
/// let my_struct = NotValuable {
/// foo: 123,
/// bar: 456,
/// };
///
/// let rendered = Renderable::Debug(&my_struct).render_to_string();
/// assert_eq!(rendered, "NotValuable { foo: 123, bar: 456 }");
/// ```
#[cfg(feature = "alloc")]
#[inline]
pub fn render_to_string(&self) -> alloc::string::String {
format!("{self:?}")
}

/// Render this [`Renderable`] to an owned [`String`], but force the
/// prettiness/alternate to be the given value
/// ```
/// use valuable::{Valuable, Value, Visit, Renderable};
/// use core::fmt;
///
/// #[derive(Debug)]
/// struct NotValuable {
/// foo: u32,
/// bar: u32,
/// }
///
/// let my_struct = NotValuable {
/// foo: 123,
/// bar: 456,
/// };
///
/// let rendered = Renderable::Debug(&my_struct)
/// .render_to_string_with_prettiness(true);
/// assert_eq!(rendered,
/// "NotValuable {
/// foo: 123,
/// bar: 456,
/// }");
/// ```
#[cfg(feature = "alloc")]
#[inline]
pub fn render_to_string_with_prettiness(&self, pretty: bool) -> alloc::string::String {
if pretty {
format!("{self:#?}")
} else {
format!("{self:?}")
}
}
}

impl<'a> Valuable for Renderable<'a> {
fn as_value(&self) -> crate::Value<'_> {
Value::Renderable(*self)
}

fn visit(&self, visit: &mut dyn crate::Visit) {
visit.visit_value(self.as_value());
}

fn visit_slice(slice: &[Self], visit: &mut dyn crate::Visit)
where
Self: Sized,
{
visit.visit_primitive_slice(Slice::Renderable(slice));
}
}
14 changes: 14 additions & 0 deletions valuable/src/slice.rs
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,20 @@ slice! {
/// ```
Isize(isize),

/// A slice containing `Renderable` values.
///
/// # Examples
///
/// ```
/// use valuable::{Slice, Renderable};
///
/// let v = Slice::Renderable(&[
/// Renderable::Debug(&"foo"),
/// Renderable::Display(&"bar")
/// ]);
/// ```
Renderable(Renderable<'a>),

/// A slice containing `str` values.
///
/// # Examples
Expand Down
Loading

0 comments on commit 989f83b

Please sign in to comment.