Skip to main content

laminar_connectors/fpml/
limits.rs

1//! Per-document resource bounds, enforced before libxml2 allocates a tree.
2
3use crate::config::ConnectorConfig;
4use crate::error::ConnectorError;
5
6/// Hard ceiling for `fpml.max.document.bytes`; libxml2 takes the size as a C `int`.
7const MAX_DOCUMENT_BYTES_CEILING: usize = 256 * 1024 * 1024;
8
9/// Bounds applied to every document. Each is checked before the work it guards.
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub struct Limits {
12    /// Complete document bytes (one file or one message).
13    pub max_document_bytes: usize,
14    /// Element nesting depth.
15    pub max_depth: usize,
16    /// Elements in one document.
17    pub max_elements: usize,
18    /// Attributes, including namespace declarations, on one element.
19    pub max_attributes_per_element: usize,
20    /// Bytes of one text node, CDATA section, comment, or attribute value.
21    pub max_text_bytes: usize,
22    /// Namespace declarations in one document.
23    pub max_namespace_declarations: usize,
24    /// `id` attributes in one document.
25    pub max_ids: usize,
26    /// `href` attributes in one document.
27    pub max_references: usize,
28    /// Rows one document may project.
29    pub max_rows: usize,
30    /// Arrow bytes staged for one document before it is published.
31    pub max_staged_bytes: usize,
32}
33
34impl Default for Limits {
35    fn default() -> Self {
36        Self {
37            max_document_bytes: 16 * 1024 * 1024,
38            max_depth: 64,
39            max_elements: 250_000,
40            max_attributes_per_element: 32,
41            max_text_bytes: 1024 * 1024,
42            max_namespace_declarations: 64,
43            max_ids: 100_000,
44            max_references: 100_000,
45            max_rows: 10_000,
46            max_staged_bytes: 64 * 1024 * 1024,
47        }
48    }
49}
50
51impl Limits {
52    /// Read `fpml.max.*` overrides.
53    ///
54    /// # Errors
55    /// Returns a configuration error for a malformed or zero bound, or a document bound above
56    /// 256 MiB.
57    pub fn from_config(config: &ConnectorConfig) -> Result<Self, ConnectorError> {
58        let defaults = Self::default();
59        let limits = Self {
60            max_document_bytes: bound(
61                config,
62                "fpml.max.document.bytes",
63                defaults.max_document_bytes,
64            )?,
65            max_depth: bound(config, "fpml.max.depth", defaults.max_depth)?,
66            max_elements: bound(config, "fpml.max.elements", defaults.max_elements)?,
67            max_attributes_per_element: bound(
68                config,
69                "fpml.max.attributes.per.element",
70                defaults.max_attributes_per_element,
71            )?,
72            max_text_bytes: bound(config, "fpml.max.text.bytes", defaults.max_text_bytes)?,
73            max_namespace_declarations: bound(
74                config,
75                "fpml.max.namespace.declarations",
76                defaults.max_namespace_declarations,
77            )?,
78            max_ids: bound(config, "fpml.max.ids", defaults.max_ids)?,
79            max_references: bound(config, "fpml.max.references", defaults.max_references)?,
80            max_rows: bound(config, "fpml.max.rows", defaults.max_rows)?,
81            max_staged_bytes: bound(config, "fpml.max.staged.bytes", defaults.max_staged_bytes)?,
82        };
83        if limits.max_document_bytes > MAX_DOCUMENT_BYTES_CEILING {
84            return Err(ConnectorError::ConfigurationError(format!(
85                "'fpml.max.document.bytes' must not exceed {MAX_DOCUMENT_BYTES_CEILING}"
86            )));
87        }
88        Ok(limits)
89    }
90}
91
92fn bound(config: &ConnectorConfig, key: &str, default: usize) -> Result<usize, ConnectorError> {
93    let value = config.get_parsed::<usize>(key)?.unwrap_or(default);
94    if value == 0 {
95        return Err(ConnectorError::ConfigurationError(format!(
96            "'{key}' must be greater than zero"
97        )));
98    }
99    Ok(value)
100}