use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct ResourcesConfig {
#[serde(default)]
pub scan_threads: usize,
#[serde(default)]
pub embed_threads: usize,
#[serde(default)]
pub max_concurrent_documents: usize,
#[serde(default = "ResourcesConfig::default_embed_batch_size")]
pub embed_batch_size: usize,
#[serde(default)]
pub max_footprint_mb: usize,
#[serde(default)]
pub document_models: DocumentModelProfile,
}
impl ResourcesConfig {
fn default_embed_batch_size() -> usize {
32
}
pub fn effective_embed_threads(&self, deprecated_alias: usize) -> usize {
if self.embed_threads != 0 {
self.embed_threads
} else {
deprecated_alias
}
}
}
impl Default for ResourcesConfig {
fn default() -> Self {
Self {
scan_threads: 0,
embed_threads: 0,
max_concurrent_documents: 0,
embed_batch_size: Self::default_embed_batch_size(),
max_footprint_mb: 0,
document_models: DocumentModelProfile::default(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Default)]
#[serde(rename_all = "snake_case")]
pub enum DocumentModelProfile {
#[default]
Full,
CodeOnly,
#[serde(rename = "none")]
None_,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_resources_config_has_expected_field_values() {
let cfg = ResourcesConfig::default();
assert_eq!(cfg.scan_threads, 0);
assert_eq!(cfg.embed_threads, 0);
assert_eq!(cfg.max_concurrent_documents, 0);
assert_eq!(cfg.embed_batch_size, 32);
assert_eq!(cfg.max_footprint_mb, 0);
assert_eq!(cfg.document_models, DocumentModelProfile::Full);
}
#[test]
fn document_model_profile_defaults_to_full() {
assert_eq!(DocumentModelProfile::default(), DocumentModelProfile::Full);
}
#[test]
fn resources_toml_roundtrips_embed_batch_size_override() {
let cfg: ResourcesConfig = toml::from_str("embed_batch_size = 8\n").expect("parse [resources] body");
assert_eq!(cfg.embed_batch_size, 8);
assert_eq!(cfg.scan_threads, 0);
assert_eq!(cfg.document_models, DocumentModelProfile::Full);
}
#[test]
fn resources_empty_toml_falls_back_to_all_defaults() {
let cfg: ResourcesConfig = toml::from_str("").expect("empty [resources] body");
assert_eq!(cfg.embed_batch_size, 32);
assert_eq!(cfg.scan_threads, 0);
assert_eq!(cfg.embed_threads, 0);
assert_eq!(cfg.max_concurrent_documents, 0);
assert_eq!(cfg.max_footprint_mb, 0);
assert_eq!(cfg.document_models, DocumentModelProfile::Full);
}
#[test]
fn document_model_profile_none_serializes_as_none_string() {
let profile = DocumentModelProfile::None_;
let json = serde_json::to_string(&profile).expect("serialize");
assert_eq!(json, "\"none\"");
let back: DocumentModelProfile = serde_json::from_str("\"none\"").expect("deserialize");
assert_eq!(back, DocumentModelProfile::None_);
}
#[test]
fn document_model_profile_code_only_uses_snake_case() {
let json = serde_json::to_string(&DocumentModelProfile::CodeOnly).expect("serialize");
assert_eq!(json, "\"code_only\"");
}
#[test]
fn effective_embed_threads_prefers_resources_then_deprecated_alias() {
let cfg = ResourcesConfig {
embed_threads: 4,
..ResourcesConfig::default()
};
assert_eq!(cfg.effective_embed_threads(8), 4);
let cfg = ResourcesConfig::default();
assert_eq!(cfg.effective_embed_threads(8), 8);
assert_eq!(cfg.effective_embed_threads(0), 0);
}
}