use serde_json::Value as JsonValue;
use std::any::Any;
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub enum TextStyle {
#[default]
Humanized,
Plain,
}
#[derive(Clone, Debug, Default)]
pub struct TextOptions {
pub style: TextStyle,
pub markdown: bool,
pub max_items: Option<usize>,
}
impl TextOptions {
pub fn new() -> Self {
Self::default()
}
pub fn with_style(mut self, style: TextStyle) -> Self {
self.style = style;
self
}
pub fn with_markdown(mut self, markdown: bool) -> Self {
self.markdown = markdown;
self
}
pub fn with_max_items(mut self, max_items: Option<usize>) -> Self {
self.max_items = max_items;
self
}
}
pub trait TextFormat: serde::Serialize {
fn fmt_text(&self, _opts: &TextOptions) -> String {
serde_json::to_string_pretty(self).unwrap_or_else(|_| "{}".to_string())
}
}
pub fn fallback_text_from_json(v: &JsonValue) -> String {
serde_json::to_string_pretty(v).unwrap_or_else(|_| v.to_string())
}
impl TextFormat for String {
fn fmt_text(&self, _opts: &TextOptions) -> String {
self.clone()
}
}
type ErasedFmtFn = fn(&dyn Any, &JsonValue, &TextOptions) -> Option<String>;
#[derive(Clone, Copy)]
pub struct ErasedFmt {
fmt_fn: Option<ErasedFmtFn>,
}
impl ErasedFmt {
pub const fn none() -> Self {
Self { fmt_fn: None }
}
pub fn format(
&self,
wire_any: &dyn Any,
data: &JsonValue,
opts: &TextOptions,
) -> Option<String> {
self.fmt_fn.and_then(|f| f(wire_any, data, opts))
}
}
impl std::fmt::Debug for ErasedFmt {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ErasedFmt")
.field("has_formatter", &self.fmt_fn.is_some())
.finish()
}
}
pub fn build_formatter_for_textformat<W>() -> ErasedFmt
where
W: TextFormat + Send + 'static,
{
ErasedFmt {
fmt_fn: Some(|any, _json, opts| any.downcast_ref::<W>().map(|w| w.fmt_text(opts))),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_text_style_default() {
let style = TextStyle::default();
assert_eq!(style, TextStyle::Humanized);
}
#[test]
fn test_text_options_default() {
let opts = TextOptions::default();
assert_eq!(opts.style, TextStyle::Humanized);
assert!(!opts.markdown);
assert!(opts.max_items.is_none());
}
#[test]
fn test_text_options_builder() {
let opts = TextOptions::new()
.with_style(TextStyle::Plain)
.with_markdown(true)
.with_max_items(Some(10));
assert_eq!(opts.style, TextStyle::Plain);
assert!(opts.markdown);
assert_eq!(opts.max_items, Some(10));
}
#[test]
fn test_fallback_text_from_json_object() {
let v = serde_json::json!({"name": "test", "count": 42});
let text = fallback_text_from_json(&v);
assert!(text.contains("\"name\": \"test\""));
assert!(text.contains("\"count\": 42"));
}
#[test]
fn test_fallback_text_from_json_array() {
let v = serde_json::json!([1, 2, 3]);
let text = fallback_text_from_json(&v);
assert!(text.contains("1"));
assert!(text.contains("2"));
assert!(text.contains("3"));
}
#[test]
fn test_fallback_text_from_json_null() {
let v = serde_json::json!(null);
let text = fallback_text_from_json(&v);
assert_eq!(text, "null");
}
#[test]
fn test_text_format_impl() {
#[derive(serde::Serialize)]
struct TestOutput {
message: String,
}
impl TextFormat for TestOutput {
fn fmt_text(&self, _opts: &TextOptions) -> String {
format!("Message: {}", self.message)
}
}
let output = TestOutput {
message: "Hello".to_string(),
};
let text = output.fmt_text(&TextOptions::default());
assert_eq!(text, "Message: Hello");
}
}