From e735671acb3a81e1b7e334e56b9ef3963ba0c2fc Mon Sep 17 00:00:00 2001 From: 魏曹先生 <1992414357@qq.com> Date: Fri, 26 Jun 2026 06:08:12 +0800 Subject: feat(core): decouple structured output from Groupped trait Introduce `StructuralData` sealed trait and `pack_structural!` / `group_structural!` / `derive(StructuralData)` macros to control structured rendering separately from grouping. `Groupped` no longer requires `Serialize`. --- mingling_core/src/renderer/general.rs | 58 +++++++++++++--------- .../src/renderer/general/structural_data.rs | 15 ++++++ 2 files changed, 50 insertions(+), 23 deletions(-) create mode 100644 mingling_core/src/renderer/general/structural_data.rs (limited to 'mingling_core/src/renderer') diff --git a/mingling_core/src/renderer/general.rs b/mingling_core/src/renderer/general.rs index 1a9647b..e6da06b 100644 --- a/mingling_core/src/renderer/general.rs +++ b/mingling_core/src/renderer/general.rs @@ -4,13 +4,16 @@ use crate::{ use serde::Serialize; pub mod error; +pub mod structural_data; + +use structural_data::StructuralData; /// A general renderer that supports multiple serialization formats. /// /// The `GeneralRenderer` provides methods to serialize data into various formats /// including JSON, YAML, TOML, and RON, with support for both regular and /// pretty-printed variants. It is designed to work with types that implement -/// the `Serialize` trait. +/// the [`StructuralData`] trait (which implies `Serialize`). pub struct GeneralRenderer; impl GeneralRenderer { @@ -20,7 +23,7 @@ impl GeneralRenderer { /// /// Returns `Err(GeneralRendererSerializeError)` if serialization fails. #[allow(unused_variables)] - pub fn render( + pub fn render( data: &T, setting: &GeneralRendererSetting, r: &mut RenderResult, @@ -48,13 +51,13 @@ impl GeneralRenderer { /// /// Returns `Err(GeneralRendererSerializeError)` if serialization fails. #[cfg(feature = "json_serde_fmt")] - pub fn render_to_json( + fn render_to_json( data: &T, r: &mut RenderResult, ) -> Result<(), GeneralRendererSerializeError> { let json_string = serde_json::to_string(data) .map_err(|e| GeneralRendererSerializeError::new(e.to_string()))?; - r.print(json_string.clone().as_str()); + r.print(&json_string); Ok(()) } @@ -64,13 +67,13 @@ impl GeneralRenderer { /// /// Returns `Err(GeneralRendererSerializeError)` if serialization fails. #[cfg(feature = "json_serde_fmt")] - pub fn render_to_json_pretty( + fn render_to_json_pretty( data: &T, r: &mut RenderResult, ) -> Result<(), GeneralRendererSerializeError> { let json_string = serde_json::to_string_pretty(data) .map_err(|e| GeneralRendererSerializeError::new(e.to_string()))?; - r.print(json_string.clone().as_str()); + r.print(&json_string); Ok(()) } @@ -80,13 +83,13 @@ impl GeneralRenderer { /// /// Returns `Err(GeneralRendererSerializeError)` if serialization fails. #[cfg(feature = "ron_serde_fmt")] - pub fn render_to_ron( + fn render_to_ron( data: &T, r: &mut RenderResult, ) -> Result<(), GeneralRendererSerializeError> { let ron_string = ron::ser::to_string(data) .map_err(|e| GeneralRendererSerializeError::new(e.to_string()))?; - r.print(ron_string.to_string().as_str()); + r.print(&ron_string); Ok(()) } @@ -96,17 +99,17 @@ impl GeneralRenderer { /// /// Returns `Err(GeneralRendererSerializeError)` if serialization fails. #[cfg(feature = "ron_serde_fmt")] - pub fn render_to_ron_pretty( + fn render_to_ron_pretty( data: &T, r: &mut RenderResult, ) -> Result<(), GeneralRendererSerializeError> { - let mut pretty_config = ron::ser::PrettyConfig::new(); - pretty_config.new_line = std::borrow::Cow::from("\n"); - pretty_config.indentor = std::borrow::Cow::from(" "); + let pretty_config = ron::ser::PrettyConfig::new() + .new_line("\n") + .indentor(" "); let ron_string = ron::ser::to_string_pretty(data, pretty_config) .map_err(|e| GeneralRendererSerializeError::new(e.to_string()))?; - r.print(ron_string.to_string().as_str()); + r.print(&ron_string); Ok(()) } @@ -116,13 +119,13 @@ impl GeneralRenderer { /// /// Returns `Err(GeneralRendererSerializeError)` if serialization fails. #[cfg(feature = "toml_serde_fmt")] - pub fn render_to_toml( + fn render_to_toml( data: &T, r: &mut RenderResult, ) -> Result<(), GeneralRendererSerializeError> { let toml_string = toml::to_string(data).map_err(|e| GeneralRendererSerializeError::new(e.to_string()))?; - r.print(toml_string.to_string().as_str()); + r.print(&toml_string); Ok(()) } @@ -132,13 +135,13 @@ impl GeneralRenderer { /// /// Returns `Err(GeneralRendererSerializeError)` if serialization fails. #[cfg(feature = "yaml_serde_fmt")] - pub fn render_to_yaml( + fn render_to_yaml( data: &T, r: &mut RenderResult, ) -> Result<(), GeneralRendererSerializeError> { let yaml_string = serde_yaml::to_string(data) .map_err(|e| GeneralRendererSerializeError::new(e.to_string()))?; - r.print(yaml_string.to_string().as_str()); + r.print(&yaml_string); Ok(()) } } @@ -155,6 +158,9 @@ mod tests { value: i32, } + impl crate::__private::StructuralDataSealed for TestData {} + impl StructuralData for TestData {} + fn test_data() -> TestData { TestData { name: "hello".into(), @@ -175,7 +181,8 @@ mod tests { #[test] fn test_render_to_json() { let mut r = RenderResult::default(); - let result = GeneralRenderer::render_to_json(&test_data(), &mut r); + let result = + GeneralRenderer::render(&test_data(), &GeneralRendererSetting::Json, &mut r); assert!(result.is_ok()); assert!(!r.is_empty()); let output: String = r.into(); @@ -189,7 +196,8 @@ mod tests { #[test] fn test_render_to_json_pretty() { let mut r = RenderResult::default(); - let result = GeneralRenderer::render_to_json_pretty(&test_data(), &mut r); + let result = + GeneralRenderer::render(&test_data(), &GeneralRendererSetting::JsonPretty, &mut r); assert!(result.is_ok()); let output: String = r.into(); // Pretty JSON has newlines @@ -200,7 +208,8 @@ mod tests { #[test] fn test_render_to_yaml() { let mut r = RenderResult::default(); - let result = GeneralRenderer::render_to_yaml(&test_data(), &mut r); + let result = + GeneralRenderer::render(&test_data(), &GeneralRendererSetting::Yaml, &mut r); assert!(result.is_ok()); assert!(!r.is_empty()); } @@ -209,7 +218,8 @@ mod tests { #[test] fn test_render_to_toml() { let mut r = RenderResult::default(); - let result = GeneralRenderer::render_to_toml(&test_data(), &mut r); + let result = + GeneralRenderer::render(&test_data(), &GeneralRendererSetting::Toml, &mut r); assert!(result.is_ok()); assert!(!r.is_empty()); } @@ -218,7 +228,8 @@ mod tests { #[test] fn test_render_to_ron() { let mut r = RenderResult::default(); - let result = GeneralRenderer::render_to_ron(&test_data(), &mut r); + let result = + GeneralRenderer::render(&test_data(), &GeneralRendererSetting::Ron, &mut r); assert!(result.is_ok()); assert!(!r.is_empty()); } @@ -227,7 +238,8 @@ mod tests { #[test] fn test_render_to_ron_pretty() { let mut r = RenderResult::default(); - let result = GeneralRenderer::render_to_ron_pretty(&test_data(), &mut r); + let result = + GeneralRenderer::render(&test_data(), &GeneralRendererSetting::RonPretty, &mut r); assert!(result.is_ok()); let output: String = r.into(); assert!(output.contains('\n')); diff --git a/mingling_core/src/renderer/general/structural_data.rs b/mingling_core/src/renderer/general/structural_data.rs new file mode 100644 index 0000000..ac6363e --- /dev/null +++ b/mingling_core/src/renderer/general/structural_data.rs @@ -0,0 +1,15 @@ +use serde::Serialize; + +/// Marker trait for types that support structured output (JSON / YAML / TOML / RON). +/// +/// This trait is a **supertrait** of `serde::Serialize` and is sealed via +/// `__private::StructuralDataSealed`. It can only be implemented through: +/// +/// - `#[derive(StructuralData)]` +/// - `pack_structural!` +/// - `group_structural!` +/// +/// These entry points also register the type in the global `STRUCTURED_TYPES` +/// registry, which is required for the `general_render` match arm to be generated. +#[doc(hidden)] +pub trait StructuralData: Serialize + crate::__private::StructuralDataSealed {} -- cgit