diff options
Diffstat (limited to 'mingling_core/src')
| -rw-r--r-- | mingling_core/src/any.rs | 31 | ||||
| -rw-r--r-- | mingling_core/src/any/group.rs | 92 | ||||
| -rw-r--r-- | mingling_core/src/lib.rs | 15 | ||||
| -rw-r--r-- | mingling_core/src/renderer/general.rs | 58 | ||||
| -rw-r--r-- | mingling_core/src/renderer/general/structural_data.rs | 15 |
5 files changed, 101 insertions, 110 deletions
diff --git a/mingling_core/src/any.rs b/mingling_core/src/any.rs index 8ee07f5..ef9f912 100644 --- a/mingling_core/src/any.rs +++ b/mingling_core/src/any.rs @@ -1,6 +1,3 @@ -#[cfg(feature = "general_renderer")] -use serde::Serialize; - use crate::Groupped; use crate::error::ChainProcessError; @@ -14,7 +11,8 @@ pub mod group; /// /// Note: /// - If an enum value that does not belong to this type is incorrectly specified, it will be **unsafely** unwrapped by the scheduler -/// - Under the `general_renderer` feature, the passed value must ensure it implements `serde::Serialize` +/// - Structured output via `--json`/`--yaml` is only available for types that implement +/// [`StructuralData`], which implies `serde::Serialize`. /// - It is recommended to use the `pack!` macro from [mingling_macros](https://crates.io/crates/mingling_macros) to create types that can be converted to `AnyOutput`, which guarantees runtime safety #[derive(Debug)] pub struct AnyOutput<G> { @@ -24,21 +22,7 @@ pub struct AnyOutput<G> { } impl<G> AnyOutput<G> { - /// Create an `AnyOutput` from a `Send + Groupped<G> + Serialize` type - #[cfg(feature = "general_renderer")] - pub fn new<T>(value: T) -> Self - where - T: Send + Groupped<G> + Serialize + 'static, - { - Self { - inner: Box::new(value), - type_id: std::any::TypeId::of::<T>(), - member_id: T::member_id(), - } - } - /// Create an `AnyOutput` from a `Send + Groupped<G>` type - #[cfg(not(feature = "general_renderer"))] pub fn new<T>(value: T) -> Self where T: Send + Groupped<G> + 'static, @@ -82,9 +66,14 @@ impl<G> AnyOutput<G> { ChainProcess::Ok((self, NextProcess::Renderer)) } - #[cfg(feature = "general_renderer")] - /// Restore `AnyOutput` back to the original Serialize type - pub fn restore<T: Serialize + 'static>(self) -> Option<T> { + /// Restore `AnyOutput` back to the original concrete type. + /// + /// # Safety + /// + /// This is only safe when `T` matches the `TypeId` stored in the `AnyOutput`. + /// Generated code (via `gen_program!()`) guarantees this by dispatching on + /// `member_id` before calling `restore`. + pub fn restore<T: 'static>(self) -> Option<T> { if self.type_id == std::any::TypeId::of::<T>() { match self.inner.downcast::<T>() { Ok(boxed) => Some(*boxed), diff --git a/mingling_core/src/any/group.rs b/mingling_core/src/any/group.rs index e9fce5e..07f8400 100644 --- a/mingling_core/src/any/group.rs +++ b/mingling_core/src/any/group.rs @@ -1,74 +1,34 @@ -#[cfg(feature = "general_renderer")] -pub use general_renderer_groupped::*; - -#[cfg(not(feature = "general_renderer"))] -pub use groupped::*; - -#[cfg(feature = "general_renderer")] -mod general_renderer_groupped { - use serde::Serialize; - - use crate::{AnyOutput, ChainProcess}; - /// Used to mark a type with a unique enum ID, assisting dynamic dispatch - pub trait Groupped<Group> +use crate::{AnyOutput, ChainProcess}; + +/// Used to mark a type with a unique enum ID, assisting dynamic dispatch +/// +/// **Note:** Unlike earlier versions, `Groupped` no longer requires `Serialize` +/// even when the `general_renderer` feature is enabled. Structured output is +/// controlled separately via the [`StructalData`] trait. +pub trait Groupped<Group> +where + Self: Sized + 'static, +{ + /// Returns the specific enum value representing its ID within that enum + fn member_id() -> Group; + + /// Converts the grouped item into a `ChainProcess` directed to the chain route. + /// + /// This wraps the item into an `AnyOutput` and routes it to the chain processing pipeline. + fn to_chain(self) -> ChainProcess<Group> where - Self: Sized + Serialize + 'static, + Self: Send, { - /// Returns the specific enum value representing its ID within that enum - fn member_id() -> Group; - - /// Converts the grouped item into a `ChainProcess` directed to the chain route. - /// - /// This wraps the item into an `AnyOutput` and routes it to the chain processing pipeline. - fn to_chain(self) -> ChainProcess<Group> - where - Self: Send + Serialize, - { - AnyOutput::new(self).route_chain() - } - - /// Converts the grouped item into a `ChainProcess` directed to the render route. - /// - /// This wraps the item into an `AnyOutput` and routes it to the render processing pipeline. - fn to_render(self) -> ChainProcess<Group> - where - Self: Send + Serialize, - { - AnyOutput::new(self).route_renderer() - } + AnyOutput::new(self).route_chain() } -} - -#[cfg(not(feature = "general_renderer"))] -mod groupped { - use crate::{AnyOutput, ChainProcess}; - /// Used to mark a type with a unique enum ID, assisting dynamic dispatch - pub trait Groupped<Group> + /// Converts the grouped item into a `ChainProcess` directed to the render route. + /// + /// This wraps the item into an `AnyOutput` and routes it to the render processing pipeline. + fn to_render(self) -> ChainProcess<Group> where - Self: Sized + 'static, + Self: Send, { - /// Returns the specific enum value representing its ID within that enum - fn member_id() -> Group; - - /// Converts the grouped item into a `ChainProcess` directed to the chain route. - /// - /// This wraps the item into an `AnyOutput` and routes it to the chain processing pipeline. - fn to_chain(self) -> ChainProcess<Group> - where - Self: Send, - { - AnyOutput::new(self).route_chain() - } - - /// Converts the grouped item into a `ChainProcess` directed to the render route. - /// - /// This wraps the item into an `AnyOutput` and routes it to the render processing pipeline. - fn to_render(self) -> ChainProcess<Group> - where - Self: Send, - { - AnyOutput::new(self).route_renderer() - } + AnyOutput::new(self).route_renderer() } } diff --git a/mingling_core/src/lib.rs b/mingling_core/src/lib.rs index ddb5446..9d0ac2a 100644 --- a/mingling_core/src/lib.rs +++ b/mingling_core/src/lib.rs @@ -22,6 +22,9 @@ pub mod test { #[cfg(feature = "general_renderer")] pub use crate::renderer::general::GeneralRenderer; +// NOT re-exported at top level: the `StructuralData` trait is sealed and only +// accessible through the derive macro. Users who need the trait can access it +// via `mingling::renderer::general::StructuralData` (through the inner alias). pub use crate::any::group::*; pub use crate::any::*; @@ -72,6 +75,18 @@ pub mod setup { pub use crate::program::setup::ProgramSetup; } +/// Private API — not intended for direct use. +#[doc(hidden)] +pub mod __private { + /// Sealed trait for `StructuralData` — only implementable via derive macro. + pub trait StructuralDataSealed {} + + /// Re-export so the derive macro can reference the trait without + /// conflicting with the derive macro name at `::mingling::StructuralData`. + #[cfg(feature = "general_renderer")] + pub use crate::renderer::general::structural_data::StructuralData; +} + #[doc(hidden)] pub mod core_res { #[cfg(feature = "repl")] 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<T: Serialize + Send>( + pub fn render<T: StructuralData + Send>( 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<T: Serialize + Send>( + fn render_to_json<T: Serialize + Send>( 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<T: Serialize + Send>( + fn render_to_json_pretty<T: Serialize + Send>( 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<T: Serialize + Send>( + fn render_to_ron<T: Serialize + Send>( 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<T: Serialize + Send>( + fn render_to_ron_pretty<T: Serialize + Send>( 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<T: Serialize + Send>( + fn render_to_toml<T: Serialize + Send>( 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<T: Serialize + Send>( + fn render_to_yaml<T: Serialize + Send>( 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 {} |
