terraphim_markdown_parser/
limits.rs1#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
9pub enum LimitKind {
10 InputBytes,
12 NestingDepth,
14 NodeCount,
16 LineLength,
18 OutputBytes,
20}
21
22impl core::fmt::Display for LimitKind {
23 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
24 let name = match self {
25 LimitKind::InputBytes => "input bytes",
26 LimitKind::NestingDepth => "nesting depth",
27 LimitKind::NodeCount => "node count",
28 LimitKind::LineLength => "line length",
29 LimitKind::OutputBytes => "output bytes",
30 };
31 f.write_str(name)
32 }
33}
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
37pub enum ParseLimitsError {
38 #[error("input exceeds limit of {limit} bytes (actual: {actual})")]
39 InputTooLarge { limit: usize, actual: usize },
40 #[error("line exceeds limit of {limit} bytes (actual: {actual})")]
41 LineTooLong { limit: usize, actual: usize },
42}
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46pub struct ParseLimits {
47 pub max_input_bytes: usize,
48 pub max_depth: usize,
49 pub max_nodes: usize,
50 pub max_line_length: usize,
51 pub max_output_bytes: usize,
52}
53
54impl Default for ParseLimits {
55 fn default() -> Self {
56 Self {
57 max_input_bytes: 4 * 1024 * 1024,
58 max_depth: 128,
59 max_nodes: 100_000,
60 max_line_length: 8 * 1024,
61 max_output_bytes: 16 * 1024 * 1024,
62 }
63 }
64}
65
66impl ParseLimits {
67 pub fn with_max_input_bytes(mut self, value: usize) -> Self {
69 self.max_input_bytes = value;
70 self
71 }
72
73 pub fn with_max_depth(mut self, value: usize) -> Self {
75 self.max_depth = value;
76 self
77 }
78
79 pub fn with_max_nodes(mut self, value: usize) -> Self {
81 self.max_nodes = value;
82 self
83 }
84
85 pub fn with_max_line_length(mut self, value: usize) -> Self {
87 self.max_line_length = value;
88 self
89 }
90
91 pub fn with_max_output_bytes(mut self, value: usize) -> Self {
93 self.max_output_bytes = value;
94 self
95 }
96
97 pub fn clamp_max_input_bytes(mut self, value: usize) -> Self {
99 self.max_input_bytes = self.max_input_bytes.min(value);
100 self
101 }
102
103 pub fn clamp_max_depth(mut self, value: usize) -> Self {
105 self.max_depth = self.max_depth.min(value);
106 self
107 }
108
109 pub fn clamp_max_nodes(mut self, value: usize) -> Self {
111 self.max_nodes = self.max_nodes.min(value);
112 self
113 }
114
115 pub fn clamp_max_line_length(mut self, value: usize) -> Self {
117 self.max_line_length = self.max_line_length.min(value);
118 self
119 }
120
121 pub fn clamp_max_output_bytes(mut self, value: usize) -> Self {
123 self.max_output_bytes = self.max_output_bytes.min(value);
124 self
125 }
126
127 pub fn validate_input(&self, markdown: &str) -> Result<(), ParseLimitsError> {
129 if markdown.len() > self.max_input_bytes {
130 return Err(ParseLimitsError::InputTooLarge {
131 limit: self.max_input_bytes,
132 actual: markdown.len(),
133 });
134 }
135 let longest = markdown
136 .split('\n')
137 .map(|line| line.len())
138 .max()
139 .unwrap_or(0);
140 if longest > self.max_line_length {
141 return Err(ParseLimitsError::LineTooLong {
142 limit: self.max_line_length,
143 actual: longest,
144 });
145 }
146 Ok(())
147 }
148}
149
150#[derive(Debug, Clone, Copy, PartialEq, Eq)]
152pub struct AstStats {
153 pub nodes: usize,
154 pub depth: usize,
155}
156
157pub fn measure_ast(root: &markdown::mdast::Node) -> Option<AstStats> {
159 fn walk(node: &markdown::mdast::Node, depth: usize, stats: &mut AstStats) -> Option<()> {
160 stats.nodes = stats.nodes.checked_add(1)?;
161 stats.depth = stats.depth.max(depth.checked_add(1)?);
162 for child in ast_children(node).into_iter().flatten() {
163 walk(child, depth.checked_add(1)?, stats)?;
164 }
165 Some(())
166 }
167 let mut stats = AstStats { nodes: 0, depth: 0 };
168 walk(root, 0, &mut stats)?;
169 Some(stats)
170}
171
172pub(crate) fn ast_children(node: &markdown::mdast::Node) -> Option<&Vec<markdown::mdast::Node>> {
174 use markdown::mdast::Node;
175 match node {
176 Node::Root(n) => Some(&n.children),
177 Node::Blockquote(n) => Some(&n.children),
178 Node::List(n) => Some(&n.children),
179 Node::ListItem(n) => Some(&n.children),
180 Node::Paragraph(n) => Some(&n.children),
181 Node::Heading(n) => Some(&n.children),
182 Node::Table(n) => Some(&n.children),
183 Node::TableRow(n) => Some(&n.children),
184 Node::TableCell(n) => Some(&n.children),
185 Node::Emphasis(n) => Some(&n.children),
186 Node::Strong(n) => Some(&n.children),
187 Node::Delete(n) => Some(&n.children),
188 Node::Link(n) => Some(&n.children),
189 Node::LinkReference(n) => Some(&n.children),
190 Node::ImageReference(_) => None,
191 Node::FootnoteDefinition(n) => Some(&n.children),
192 Node::FootnoteReference(_) => None,
193 Node::Toml(_) => None,
194 Node::Yaml(_) => None,
195 Node::Break(_) => None,
196 Node::Code(_) => None,
197 Node::Definition(_) => None,
198 Node::Html(_) => None,
199 Node::Image(_) => None,
200 Node::InlineCode(_) => None,
201 Node::InlineMath(_) => None,
202 Node::MdxJsxFlowElement(_) => None,
203 Node::MdxjsEsm(_) => None,
204 Node::MdxTextExpression(_) => None,
205 Node::MdxFlowExpression(_) => None,
206 Node::MdxJsxTextElement(_) => None,
207 Node::Math(_) => None,
208 Node::Text(_) => None,
209 Node::ThematicBreak(_) => None,
210 }
211}