aboutsummaryrefslogtreecommitdiff
path: root/mingling_core
diff options
context:
space:
mode:
Diffstat (limited to 'mingling_core')
-rw-r--r--mingling_core/src/any.rs31
-rw-r--r--mingling_core/src/any/group.rs92
-rw-r--r--mingling_core/src/lib.rs15
-rw-r--r--mingling_core/src/renderer/general.rs58
-rw-r--r--mingling_core/src/renderer/general/structural_data.rs15
-rw-r--r--mingling_core/tests/test-all/tests/integration.rs3
-rw-r--r--mingling_core/tests/test-general-renderer/tests/integration.rs6
7 files changed, 105 insertions, 115 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 {}
diff --git a/mingling_core/tests/test-all/tests/integration.rs b/mingling_core/tests/test-all/tests/integration.rs
index e173374..99910a9 100644
--- a/mingling_core/tests/test-all/tests/integration.rs
+++ b/mingling_core/tests/test-all/tests/integration.rs
@@ -3,6 +3,7 @@ use mingling::GeneralRenderer;
use mingling::GeneralRendererSetting;
use mingling::MockProgramCollect;
use mingling::NextProcess;
+use mingling::StructuralData;
use mingling::Node;
use mingling::Program;
use mingling::RenderResult;
@@ -90,7 +91,7 @@ fn test_render_result_print() {
// GeneralRenderer
-#[derive(Debug, Clone, PartialEq, Serialize)]
+#[derive(Debug, Clone, PartialEq, Serialize, StructuralData)]
struct TestData {
name: String,
value: i32,
diff --git a/mingling_core/tests/test-general-renderer/tests/integration.rs b/mingling_core/tests/test-general-renderer/tests/integration.rs
index 0fcc38d..2e2472e 100644
--- a/mingling_core/tests/test-general-renderer/tests/integration.rs
+++ b/mingling_core/tests/test-general-renderer/tests/integration.rs
@@ -1,9 +1,7 @@
-use mingling::GeneralRenderer;
-use mingling::GeneralRendererSetting;
-use mingling::RenderResult;
+use mingling::{GeneralRenderer, GeneralRendererSetting, RenderResult, StructuralData};
use serde::Serialize;
-#[derive(Debug, Clone, PartialEq, Serialize)]
+#[derive(Debug, Clone, PartialEq, Serialize, StructuralData)]
struct TestData {
name: String,
value: i32,