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 unique list of custom component names (elements starting with capital letters)
44    Schema,
45    /// Return JSON representation of the transformed JSX tree
46    Json,
47}
48
49/// Rendering settings
50#[derive(Deserialize, Serialize, Clone, Debug)]
51pub struct RenderSettings {
52    /// Output format (html, javascript, schema, or json)
53    #[serde(default)]
54    pub output: OutputFormat,
55    /// Enable minification
56    #[serde(default = "default_minify_true")]
57    pub minify: bool,
58    /// Optional JavaScript snippet to inject as global utils
59    /// Must use `export default` to return a single object
60    /// Example: `export default { tool: "foo", etc: "bar" }`
61    #[serde(skip_serializing_if = "Option::is_none")]
62    pub utils: Option<String>,
63    /// Optional list of directive prefixes for schema extraction
64    /// Used to identify directive attributes (e.g., ["v-", "@", "x-"])
65    #[serde(skip_serializing_if = "Option::is_none")]
66    pub directives: Option<Vec<String>>,
67}
68
69const fn default_minify_true() -> bool {
70    true
71}
72
73impl Default for RenderSettings {
74    fn default() -> Self {
75        Self {
76            output: OutputFormat::default(),
77            minify: true,
78            utils: None,
79            directives: None,
80        }
81    }
82}
83
84/// Input structure for batch MDX rendering requests
85#[derive(Deserialize, Serialize)]
86pub struct NamedMdxBatchInput {
87    /// Rendering settings
88    #[serde(default)]
89    pub settings: RenderSettings,
90    /// Map of file names to MDX content strings
91    pub mdx: HashMap<String, String>,
92    /// Optional map of component names to their definitions
93    #[serde(skip_serializing_if = "Option::is_none")]
94    pub components: Option<HashMap<String, ComponentDefinition>>,
95}
96
97/// Output structure containing rendered output and metadata
98#[derive(Serialize, Deserialize, Debug)]
99pub struct RenderedMdx {
100    /// Parsed YAML frontmatter metadata
101    pub metadata: serde_json::Value,
102    /// Rendered output content (HTML or JavaScript depending on output format)
103    ///
104    /// This field always contains the rendered result, regardless of output format.
105    /// The format is determined by the `output` setting in the render request:
106    /// - `OutputFormat::Html` → HTML string
107    /// - `OutputFormat::Javascript` → JavaScript code
108    /// - `OutputFormat::Schema` → JavaScript code after TSX transformation (before rendering)
109    #[serde(skip_serializing_if = "Option::is_none")]
110    pub output: Option<String>,
111}
112
113/// Resource limits for preventing resource exhaustion.
114///
115/// These limits are enforced at the library level to prevent memory exhaustion
116/// and ensure reliable operation. They are not HTTP security controls, but rather
117/// internal reliability measures.
118#[derive(Clone, Debug)]
119pub struct ResourceLimits {
120    /// Maximum number of files in a batch request
121    pub max_batch_size: usize,
122    /// Maximum MDX content size per file (in bytes)
123    pub max_mdx_content_size: usize,
124    /// Maximum component code size (in bytes)
125    pub max_component_code_size: usize,
126}
127
128impl Default for ResourceLimits {
129    fn default() -> Self {
130        Self {
131            max_batch_size: 1000,
132            max_mdx_content_size: 10 * 1024 * 1024, // 10 MB
133            max_component_code_size: 1024 * 1024,   // 1 MB
134        }
135    }
136}
137
138impl ResourceLimits {
139    /// Validates resource limits and returns an error if invalid.
140    ///
141    /// # Returns
142    /// `Ok(())` if limits are valid, `Err` with a descriptive message if invalid.
143    pub fn validate(&self) -> Result<(), String> {
144        if self.max_batch_size == 0 {
145            return Err("max_batch_size must be greater than 0".to_string());
146        }
147
148        if self.max_mdx_content_size == 0 {
149            return Err("max_mdx_content_size must be greater than 0".to_string());
150        }
151
152        if self.max_component_code_size == 0 {
153            return Err("max_component_code_size must be greater than 0".to_string());
154        }
155
156        // Enforce maximum recommended limits to prevent memory exhaustion
157        const MAX_RECOMMENDED_BATCH_SIZE: usize = 100_000;
158        if self.max_batch_size > MAX_RECOMMENDED_BATCH_SIZE {
159            return Err(format!(
160                "max_batch_size ({}) exceeds recommended maximum of {}",
161                self.max_batch_size, MAX_RECOMMENDED_BATCH_SIZE
162            ));
163        }
164
165        const MAX_RECOMMENDED_MDX_CONTENT_SIZE: usize = 100 * 1024 * 1024; // 100 MB
166        if self.max_mdx_content_size > MAX_RECOMMENDED_MDX_CONTENT_SIZE {
167            return Err(format!(
168                "max_mdx_content_size ({}) exceeds recommended maximum of {} bytes (100 MB)",
169                self.max_mdx_content_size, MAX_RECOMMENDED_MDX_CONTENT_SIZE
170            ));
171        }
172
173        Ok(())
174    }
175}
176
177/// Configuration for TSX transformation
178pub struct TsxTransformConfig {
179    /// JSX pragma function name (e.g., "engine.h" or "h")
180    pub jsx_pragma: String,
181    /// JSX fragment pragma (e.g., "engine.Fragment" or "Fragment")
182    pub jsx_pragma_frag: String,
183    /// Whether to minify the output JavaScript
184    pub minify: bool,
185    /// Component names to convert from function references to strings (for schema output)
186    pub component_names: Option<std::collections::HashSet<String>>,
187}
188
189impl Default for TsxTransformConfig {
190    fn default() -> Self {
191        Self {
192            jsx_pragma: "engine.h".to_string(),
193            jsx_pragma_frag: "engine.Fragment".to_string(),
194            minify: false,
195            component_names: None,
196        }
197    }
198}
199
200impl TsxTransformConfig {
201    /// Configuration for final output transformation (uses `h` instead of `engine.h`)
202    pub fn for_output(minify: bool) -> Self {
203        Self {
204            jsx_pragma: "h".to_string(),
205            jsx_pragma_frag: "Fragment".to_string(),
206            minify,
207            component_names: None,
208        }
209    }
210
211    /// Helper that clones the default config but toggles minification.
212    pub fn for_engine(minify: bool) -> Self {
213        Self {
214            minify,
215            component_names: None,
216            ..Self::default()
217        }
218    }
219}