use std::borrow::Cow;
const BYTES_PER_MIB: usize = 1024 * 1024;
pub const RECOMMENDED_MAX_SOURCE_BYTES: usize = 16 * BYTES_PER_MIB;
pub const RECOMMENDED_PARSE_TIMEOUT_MS: u64 = 5_000;
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ProcessConfig {
pub language: Cow<'static, str>,
#[cfg_attr(feature = "serde", serde(default = "default_true"))]
pub structure: bool,
#[cfg_attr(feature = "serde", serde(default = "default_true"))]
pub imports: bool,
#[cfg_attr(feature = "serde", serde(default = "default_true"))]
pub exports: bool,
#[cfg_attr(feature = "serde", serde(default))]
pub comments: bool,
#[cfg_attr(feature = "serde", serde(default))]
pub docstrings: bool,
#[cfg_attr(feature = "serde", serde(default))]
pub symbols: bool,
#[cfg_attr(feature = "serde", serde(default))]
pub diagnostics: bool,
#[cfg_attr(feature = "serde", serde(default))]
pub chunk_max_size: Option<usize>,
#[cfg_attr(feature = "serde", serde(default))]
pub data_extraction: bool,
#[cfg_attr(feature = "serde", serde(default))]
pub max_source_bytes: Option<usize>,
#[cfg_attr(feature = "serde", serde(default))]
pub parse_timeout_ms: Option<u64>,
}
#[cfg(feature = "serde")]
fn default_true() -> bool {
true
}
impl Default for ProcessConfig {
fn default() -> Self {
Self {
language: Cow::Borrowed(""),
structure: true,
imports: true,
exports: true,
comments: false,
docstrings: false,
symbols: false,
diagnostics: false,
chunk_max_size: None,
data_extraction: false,
max_source_bytes: None,
parse_timeout_ms: None,
}
}
}
impl ProcessConfig {
pub fn new(language: impl Into<String>) -> Self {
Self {
language: Cow::Owned(language.into()),
..Default::default()
}
}
pub fn with_chunking(mut self, max_size: usize) -> Self {
self.chunk_max_size = Some(max_size);
self
}
pub fn all(mut self) -> Self {
self.structure = true;
self.imports = true;
self.exports = true;
self.comments = true;
self.docstrings = true;
self.symbols = true;
self.diagnostics = true;
self.data_extraction = true;
self
}
pub fn minimal(mut self) -> Self {
self.structure = false;
self.imports = false;
self.exports = false;
self.comments = false;
self.docstrings = false;
self.symbols = false;
self.diagnostics = false;
self.data_extraction = false;
self
}
pub fn with_data_extraction(mut self, enabled: bool) -> Self {
self.data_extraction = enabled;
self
}
pub fn with_max_source_bytes(mut self, max_bytes: Option<usize>) -> Self {
self.max_source_bytes = max_bytes;
self
}
pub fn with_parse_timeout_ms(mut self, timeout_ms: Option<u64>) -> Self {
self.parse_timeout_ms = timeout_ms;
self
}
pub fn validate(&self) -> Result<(), crate::Error> {
if self.chunk_max_size == Some(0) {
return Err(crate::Error::InvalidRange(
"chunk_max_size must be greater than 0; use None to disable chunking".to_string(),
));
}
if self.max_source_bytes == Some(0) {
return Err(crate::Error::InvalidRange(
"max_source_bytes must be greater than 0; use None for no source-size limit".to_string(),
));
}
if self.parse_timeout_ms == Some(0) {
return Err(crate::Error::InvalidRange(
"parse_timeout_ms must be greater than 0; use None for no parse timeout".to_string(),
));
}
Ok(())
}
pub(crate) fn check_source_size(&self, source_bytes: usize) -> Result<(), crate::Error> {
let Some(limit) = self.max_source_bytes else {
return Ok(());
};
if source_bytes <= limit {
return Ok(());
}
tracing::warn!(
language = %self.language,
source_bytes,
limit,
"source exceeds the configured max_source_bytes; refusing to parse"
);
Err(crate::Error::InvalidRange(format!(
"source is {source_bytes} bytes, which exceeds the configured max_source_bytes of {limit}"
)))
}
}