#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum LimitKind {
InputBytes,
NestingDepth,
NodeCount,
LineLength,
OutputBytes,
}
impl core::fmt::Display for LimitKind {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let name = match self {
LimitKind::InputBytes => "input bytes",
LimitKind::NestingDepth => "nesting depth",
LimitKind::NodeCount => "node count",
LimitKind::LineLength => "line length",
LimitKind::OutputBytes => "output bytes",
};
f.write_str(name)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
pub enum ParseLimitsError {
#[error("input exceeds limit of {limit} bytes (actual: {actual})")]
InputTooLarge { limit: usize, actual: usize },
#[error("line exceeds limit of {limit} bytes (actual: {actual})")]
LineTooLong { limit: usize, actual: usize },
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ParseLimits {
pub max_input_bytes: usize,
pub max_depth: usize,
pub max_nodes: usize,
pub max_line_length: usize,
pub max_output_bytes: usize,
}
impl Default for ParseLimits {
fn default() -> Self {
Self {
max_input_bytes: 4 * 1024 * 1024,
max_depth: 128,
max_nodes: 100_000,
max_line_length: 8 * 1024,
max_output_bytes: 16 * 1024 * 1024,
}
}
}
impl ParseLimits {
pub fn with_max_input_bytes(mut self, value: usize) -> Self {
self.max_input_bytes = value;
self
}
pub fn with_max_depth(mut self, value: usize) -> Self {
self.max_depth = value;
self
}
pub fn with_max_nodes(mut self, value: usize) -> Self {
self.max_nodes = value;
self
}
pub fn with_max_line_length(mut self, value: usize) -> Self {
self.max_line_length = value;
self
}
pub fn with_max_output_bytes(mut self, value: usize) -> Self {
self.max_output_bytes = value;
self
}
pub fn clamp_max_input_bytes(mut self, value: usize) -> Self {
self.max_input_bytes = self.max_input_bytes.min(value);
self
}
pub fn clamp_max_depth(mut self, value: usize) -> Self {
self.max_depth = self.max_depth.min(value);
self
}
pub fn clamp_max_nodes(mut self, value: usize) -> Self {
self.max_nodes = self.max_nodes.min(value);
self
}
pub fn clamp_max_line_length(mut self, value: usize) -> Self {
self.max_line_length = self.max_line_length.min(value);
self
}
pub fn clamp_max_output_bytes(mut self, value: usize) -> Self {
self.max_output_bytes = self.max_output_bytes.min(value);
self
}
pub fn validate_input(&self, markdown: &str) -> Result<(), ParseLimitsError> {
if markdown.len() > self.max_input_bytes {
return Err(ParseLimitsError::InputTooLarge {
limit: self.max_input_bytes,
actual: markdown.len(),
});
}
let longest = markdown
.split('\n')
.map(|line| line.len())
.max()
.unwrap_or(0);
if longest > self.max_line_length {
return Err(ParseLimitsError::LineTooLong {
limit: self.max_line_length,
actual: longest,
});
}
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AstStats {
pub nodes: usize,
pub depth: usize,
}
pub fn measure_ast(root: &markdown::mdast::Node) -> Option<AstStats> {
fn walk(node: &markdown::mdast::Node, depth: usize, stats: &mut AstStats) -> Option<()> {
stats.nodes = stats.nodes.checked_add(1)?;
stats.depth = stats.depth.max(depth.checked_add(1)?);
for child in ast_children(node).into_iter().flatten() {
walk(child, depth.checked_add(1)?, stats)?;
}
Some(())
}
let mut stats = AstStats { nodes: 0, depth: 0 };
walk(root, 0, &mut stats)?;
Some(stats)
}
pub(crate) fn ast_children(node: &markdown::mdast::Node) -> Option<&Vec<markdown::mdast::Node>> {
use markdown::mdast::Node;
match node {
Node::Root(n) => Some(&n.children),
Node::Blockquote(n) => Some(&n.children),
Node::List(n) => Some(&n.children),
Node::ListItem(n) => Some(&n.children),
Node::Paragraph(n) => Some(&n.children),
Node::Heading(n) => Some(&n.children),
Node::Table(n) => Some(&n.children),
Node::TableRow(n) => Some(&n.children),
Node::TableCell(n) => Some(&n.children),
Node::Emphasis(n) => Some(&n.children),
Node::Strong(n) => Some(&n.children),
Node::Delete(n) => Some(&n.children),
Node::Link(n) => Some(&n.children),
Node::LinkReference(n) => Some(&n.children),
Node::ImageReference(_) => None,
Node::FootnoteDefinition(n) => Some(&n.children),
Node::FootnoteReference(_) => None,
Node::Toml(_) => None,
Node::Yaml(_) => None,
Node::Break(_) => None,
Node::Code(_) => None,
Node::Definition(_) => None,
Node::Html(_) => None,
Node::Image(_) => None,
Node::InlineCode(_) => None,
Node::InlineMath(_) => None,
Node::MdxJsxFlowElement(_) => None,
Node::MdxjsEsm(_) => None,
Node::MdxTextExpression(_) => None,
Node::MdxFlowExpression(_) => None,
Node::MdxJsxTextElement(_) => None,
Node::Math(_) => None,
Node::Text(_) => None,
Node::ThematicBreak(_) => None,
}
}