dinja_core/
models.rs

1//! Data structures for MDX processing
2//!
3//! This module defines the core data structures used throughout the library:
4//!
5//! - **Component Definitions**: Component code and metadata
6//! - **Render Settings**: Output format and minification options
7//! - **Resource Limits**: Configuration for preventing resource exhaustion
8//! - **Batch Input/Output**: Structures for batch rendering operations
9//!
10//! ## Resource Limits
11//!
12//! Resource limits are enforced at the library level to prevent memory exhaustion.
13//! These are reliability measures, not security controls (security is handled at the web layer).
14
15use serde::{Deserialize, Serialize};
16use std::collections::HashMap;
17
18/// Component definition with code and metadata
19#[derive(Deserialize, Serialize, Clone, Debug)]
20pub struct ComponentDefinition {
21    /// Component name (optional, defaults to HashMap key)
22    #[serde(skip_serializing_if = "Option::is_none")]
23    pub name: Option<String>,
24    /// Component documentation (metadata)
25    #[serde(skip_serializing_if = "Option::is_none")]
26    pub docs: Option<String>,
27    /// Component arguments/props types (metadata)
28    #[serde(skip_serializing_if = "Option::is_none")]
29    pub args: Option<serde_json::Value>,
30    /// Component code (JSX/TSX)
31    pub code: String,
32}
33
34/// Output format options
35#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Default)]
36#[serde(rename_all = "lowercase")]
37pub enum OutputFormat {
38    /// Return HTML template
39    #[default]
40    Html,
41    /// Return JavaScript (transform template back to JS)
42    Javascript,
43    /// Return JavaScript code after TSX transformation (before rendering)
44    Schema,
45}
46
47/// Rendering engine selection
48#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq, Default)]
49#[serde(rename_all = "lowercase")]
50pub enum RenderEngine {
51    /// Use built-in base engine components
52    #[default]
53    Base,
54    /// Use user-provided custom components
55    Custom,
56}
57
58/// Rendering settings
59#[derive(Deserialize, Serialize, Clone, Debug)]
60pub struct RenderSettings {
61    /// Output format (html, javascript, or schema)
62    #[serde(default)]
63    pub output: OutputFormat,
64    /// Enable minification
65    #[serde(default = "default_minify_true")]
66    pub minify: bool,
67    /// Rendering engine selection ("base" | "custom")
68    #[serde(default)]
69    pub engine: RenderEngine,
70    /// Component names to autopopulate when using the base engine
71    #[serde(default, skip_serializing_if = "Vec::is_empty")]
72    pub components: Vec<String>,
73}
74
75const fn default_minify_true() -> bool {
76    true
77}
78
79impl Default for RenderSettings {
80    fn default() -> Self {
81        Self {
82            output: OutputFormat::default(),
83            minify: true,
84            engine: RenderEngine::default(),
85            components: Vec::new(),
86        }
87    }
88}
89
90/// Input structure for batch MDX rendering requests
91#[derive(Deserialize, Serialize)]
92pub struct NamedMdxBatchInput {
93    /// Rendering settings
94    #[serde(default)]
95    pub settings: RenderSettings,
96    /// Map of file names to MDX content strings
97    pub mdx: HashMap<String, String>,
98    /// Optional map of component names to their definitions
99    #[serde(skip_serializing_if = "Option::is_none")]
100    pub components: Option<HashMap<String, ComponentDefinition>>,
101}
102
103/// Output structure containing rendered output and metadata
104#[derive(Serialize, Deserialize, Debug)]
105pub struct RenderedMdx {
106    /// Parsed YAML frontmatter metadata
107    pub metadata: serde_json::Value,
108    /// Rendered output content (HTML or JavaScript depending on output format)
109    ///
110    /// This field always contains the rendered result, regardless of output format.
111    /// The format is determined by the `output` setting in the render request:
112    /// - `OutputFormat::Html` → HTML string
113    /// - `OutputFormat::Javascript` → JavaScript code
114    /// - `OutputFormat::Schema` → JavaScript code after TSX transformation (before rendering)
115    #[serde(skip_serializing_if = "Option::is_none")]
116    pub output: Option<String>,
117}
118
119/// Resource limits for preventing resource exhaustion.
120///
121/// These limits are enforced at the library level to prevent memory exhaustion
122/// and ensure reliable operation. They are not HTTP security controls, but rather
123/// internal reliability measures.
124#[derive(Clone, Debug)]
125pub struct ResourceLimits {
126    /// Maximum number of files in a batch request
127    pub max_batch_size: usize,
128    /// Maximum MDX content size per file (in bytes)
129    pub max_mdx_content_size: usize,
130    /// Maximum component code size (in bytes)
131    pub max_component_code_size: usize,
132}
133
134impl Default for ResourceLimits {
135    fn default() -> Self {
136        Self {
137            max_batch_size: 1000,
138            max_mdx_content_size: 10 * 1024 * 1024, // 10 MB
139            max_component_code_size: 1024 * 1024,   // 1 MB
140        }
141    }
142}
143
144impl ResourceLimits {
145    /// Validates resource limits and returns an error if invalid.
146    ///
147    /// # Returns
148    /// `Ok(())` if limits are valid, `Err` with a descriptive message if invalid.
149    pub fn validate(&self) -> Result<(), String> {
150        if self.max_batch_size == 0 {
151            return Err("max_batch_size must be greater than 0".to_string());
152        }
153
154        if self.max_mdx_content_size == 0 {
155            return Err("max_mdx_content_size must be greater than 0".to_string());
156        }
157
158        if self.max_component_code_size == 0 {
159            return Err("max_component_code_size must be greater than 0".to_string());
160        }
161
162        // Enforce maximum recommended limits to prevent memory exhaustion
163        const MAX_RECOMMENDED_BATCH_SIZE: usize = 100_000;
164        if self.max_batch_size > MAX_RECOMMENDED_BATCH_SIZE {
165            return Err(format!(
166                "max_batch_size ({}) exceeds recommended maximum of {}",
167                self.max_batch_size, MAX_RECOMMENDED_BATCH_SIZE
168            ));
169        }
170
171        const MAX_RECOMMENDED_MDX_CONTENT_SIZE: usize = 100 * 1024 * 1024; // 100 MB
172        if self.max_mdx_content_size > MAX_RECOMMENDED_MDX_CONTENT_SIZE {
173            return Err(format!(
174                "max_mdx_content_size ({}) exceeds recommended maximum of {} bytes (100 MB)",
175                self.max_mdx_content_size, MAX_RECOMMENDED_MDX_CONTENT_SIZE
176            ));
177        }
178
179        Ok(())
180    }
181}
182
183/// Configuration for TSX transformation
184pub struct TsxTransformConfig {
185    /// JSX pragma function name (e.g., "engine.h" or "h")
186    pub jsx_pragma: String,
187    /// JSX fragment pragma (e.g., "engine.Fragment" or "Fragment")
188    pub jsx_pragma_frag: String,
189    /// Whether to minify the output JavaScript
190    pub minify: bool,
191    /// Component names to convert from function references to strings (for schema output)
192    pub component_names: Option<std::collections::HashSet<String>>,
193}
194
195impl Default for TsxTransformConfig {
196    fn default() -> Self {
197        Self {
198            jsx_pragma: "engine.h".to_string(),
199            jsx_pragma_frag: "engine.Fragment".to_string(),
200            minify: false,
201            component_names: None,
202        }
203    }
204}
205
206impl TsxTransformConfig {
207    /// Configuration for final output transformation (uses `h` instead of `engine.h`)
208    pub fn for_output(minify: bool) -> Self {
209        Self {
210            jsx_pragma: "h".to_string(),
211            jsx_pragma_frag: "Fragment".to_string(),
212            minify,
213            component_names: None,
214        }
215    }
216
217    /// Helper that clones the default config but toggles minification.
218    pub fn for_engine(minify: bool) -> Self {
219        Self {
220            minify,
221            component_names: None,
222            ..Self::default()
223        }
224    }
225}