164 lines
5.4 KiB
Rust
164 lines
5.4 KiB
Rust
use crate::JobConfig;
|
|
use crate::job::JobConfiguration;
|
|
use crate::util::DatabuildError;
|
|
use std::fs;
|
|
use std::path::Path;
|
|
|
|
/// Default idle timeout in seconds (1 hour)
|
|
pub const DEFAULT_IDLE_TIMEOUT_SECONDS: u64 = 3600;
|
|
|
|
/// Configuration file format for DataBuild application
|
|
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
|
|
pub struct DatabuildConfig {
|
|
/// Unique identifier for this graph, used for `.databuild/${graph_label}/` directory
|
|
pub graph_label: String,
|
|
|
|
/// Server auto-shutdown after this many seconds of inactivity (default: 3600)
|
|
#[serde(default = "default_idle_timeout")]
|
|
pub idle_timeout_seconds: u64,
|
|
|
|
/// BEL storage URI. Defaults to `.databuild/${graph_label}/bel.sqlite` if not specified.
|
|
///
|
|
/// Supported formats:
|
|
/// - Relative path: `path/to/bel.sqlite` (resolved relative to config file)
|
|
/// - Absolute path: `/var/data/bel.sqlite`
|
|
/// - SQLite URI: `sqlite:///path/to/bel.sqlite` or `sqlite::memory:`
|
|
/// - Future: `postgresql://user:pass@host/db`
|
|
#[serde(default)]
|
|
pub bel_uri: Option<String>,
|
|
|
|
/// List of job configurations
|
|
#[serde(default)]
|
|
pub jobs: Vec<JobConfig>,
|
|
}
|
|
|
|
fn default_idle_timeout() -> u64 {
|
|
DEFAULT_IDLE_TIMEOUT_SECONDS
|
|
}
|
|
|
|
impl DatabuildConfig {
|
|
/// Load configuration from a file, auto-detecting format from extension
|
|
pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self, DatabuildError> {
|
|
let path = path.as_ref();
|
|
let contents = fs::read_to_string(path)
|
|
.map_err(|e| DatabuildError::from(format!("Failed to read config file: {}", e)))?;
|
|
|
|
// Determine format from file extension
|
|
let extension = path.extension().and_then(|s| s.to_str()).unwrap_or("");
|
|
|
|
match extension {
|
|
"json" => Self::from_json(&contents),
|
|
"toml" => Self::from_toml(&contents),
|
|
_ => Err(DatabuildError::from(format!(
|
|
"Unknown config file extension: {}. Use .json or .toml",
|
|
extension
|
|
))),
|
|
}
|
|
}
|
|
|
|
/// Parse configuration from JSON string
|
|
pub fn from_json(s: &str) -> Result<Self, DatabuildError> {
|
|
serde_json::from_str(s)
|
|
.map_err(|e| DatabuildError::from(format!("Failed to parse JSON config: {}", e)))
|
|
}
|
|
|
|
/// Parse configuration from TOML string
|
|
pub fn from_toml(s: &str) -> Result<Self, DatabuildError> {
|
|
toml::from_str(s)
|
|
.map_err(|e| DatabuildError::from(format!("Failed to parse TOML config: {}", e)))
|
|
}
|
|
|
|
/// Convert to a list of JobConfiguration
|
|
pub fn into_job_configurations(self) -> Vec<JobConfiguration> {
|
|
self.jobs.into_iter().map(|jc| jc.into()).collect()
|
|
}
|
|
|
|
/// Get the effective BEL URI, resolving defaults based on graph_label.
|
|
///
|
|
/// If `bel_uri` is not set, returns the default path `.databuild/${graph_label}/bel.sqlite`.
|
|
/// Relative paths are not resolved here - that's the caller's responsibility.
|
|
pub fn effective_bel_uri(&self) -> String {
|
|
self.bel_uri
|
|
.clone()
|
|
.unwrap_or_else(|| format!(".databuild/{}/bel.sqlite", self.graph_label))
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_parse_json_config() {
|
|
let json = r#"
|
|
{
|
|
"graph_label": "test_graph",
|
|
"jobs": [
|
|
{
|
|
"label": "//test:job_alpha",
|
|
"entrypoint": "/usr/bin/python3",
|
|
"environment": {"FOO": "bar"},
|
|
"partition_patterns": ["data/alpha/.*"]
|
|
}
|
|
]
|
|
}
|
|
"#;
|
|
|
|
let config = DatabuildConfig::from_json(json).unwrap();
|
|
assert_eq!(config.graph_label, "test_graph");
|
|
assert_eq!(config.idle_timeout_seconds, DEFAULT_IDLE_TIMEOUT_SECONDS);
|
|
assert_eq!(config.jobs.len(), 1);
|
|
assert_eq!(config.jobs[0].label, "//test:job_alpha");
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_json_config_with_idle_timeout() {
|
|
let json = r#"
|
|
{
|
|
"graph_label": "test_graph",
|
|
"idle_timeout_seconds": 7200,
|
|
"jobs": []
|
|
}
|
|
"#;
|
|
|
|
let config = DatabuildConfig::from_json(json).unwrap();
|
|
assert_eq!(config.graph_label, "test_graph");
|
|
assert_eq!(config.idle_timeout_seconds, 7200);
|
|
assert_eq!(config.jobs.len(), 0);
|
|
}
|
|
|
|
#[test]
|
|
fn test_effective_bel_uri() {
|
|
// Default: derives from graph_label
|
|
let config = DatabuildConfig::from_json(r#"{ "graph_label": "my_graph" }"#).unwrap();
|
|
assert_eq!(config.effective_bel_uri(), ".databuild/my_graph/bel.sqlite");
|
|
|
|
// Custom: uses provided value
|
|
let config = DatabuildConfig::from_json(
|
|
r#"{ "graph_label": "my_graph", "bel_uri": "postgresql://localhost/db" }"#,
|
|
)
|
|
.unwrap();
|
|
assert_eq!(config.effective_bel_uri(), "postgresql://localhost/db");
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_toml_config() {
|
|
let toml = r#"
|
|
graph_label = "test_graph"
|
|
|
|
[[jobs]]
|
|
label = "//test:job_alpha"
|
|
entrypoint = "/usr/bin/python3"
|
|
partition_patterns = ["data/alpha/.*"]
|
|
|
|
[jobs.environment]
|
|
FOO = "bar"
|
|
"#;
|
|
|
|
let config = DatabuildConfig::from_toml(toml).unwrap();
|
|
assert_eq!(config.graph_label, "test_graph");
|
|
assert_eq!(config.idle_timeout_seconds, DEFAULT_IDLE_TIMEOUT_SECONDS);
|
|
assert_eq!(config.jobs.len(), 1);
|
|
assert_eq!(config.jobs[0].label, "//test:job_alpha");
|
|
}
|
|
}
|