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}