re-uploading work

This commit is contained in:
2026-02-04 17:46:30 -06:00
commit 3b14c65998
1388 changed files with 381262 additions and 0 deletions

View File

@@ -0,0 +1,200 @@
//! Configuration module for timer sensor
//!
//! Supports loading configuration from environment variables or stdin JSON.
use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
use std::io::Read;
/// Sensor configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SensorConfig {
/// Base URL of the Attune API
pub api_url: String,
/// API token for authentication
pub api_token: String,
/// Sensor reference name (e.g., "core.timer")
pub sensor_ref: String,
/// RabbitMQ connection URL
pub mq_url: String,
/// RabbitMQ exchange name (default: "attune")
#[serde(default = "default_exchange")]
pub mq_exchange: String,
/// Log level (default: "info")
#[serde(default = "default_log_level")]
pub log_level: String,
}
fn default_exchange() -> String {
"attune".to_string()
}
fn default_log_level() -> String {
"info".to_string()
}
impl SensorConfig {
/// Load configuration from environment variables
pub fn from_env() -> Result<Self> {
let api_url = std::env::var("ATTUNE_API_URL")
.context("ATTUNE_API_URL environment variable is required")?;
let api_token = std::env::var("ATTUNE_API_TOKEN")
.context("ATTUNE_API_TOKEN environment variable is required")?;
let sensor_ref = std::env::var("ATTUNE_SENSOR_REF")
.context("ATTUNE_SENSOR_REF environment variable is required")?;
let mq_url = std::env::var("ATTUNE_MQ_URL")
.context("ATTUNE_MQ_URL environment variable is required")?;
let mq_exchange =
std::env::var("ATTUNE_MQ_EXCHANGE").unwrap_or_else(|_| default_exchange());
let log_level = std::env::var("ATTUNE_LOG_LEVEL").unwrap_or_else(|_| default_log_level());
Ok(Self {
api_url,
api_token,
sensor_ref,
mq_url,
mq_exchange,
log_level,
})
}
/// Load configuration from stdin JSON
pub async fn from_stdin() -> Result<Self> {
let mut buffer = String::new();
std::io::stdin()
.read_to_string(&mut buffer)
.context("Failed to read configuration from stdin")?;
serde_json::from_str(&buffer).context("Failed to parse JSON configuration from stdin")
}
/// Validate configuration
pub fn validate(&self) -> Result<()> {
if self.api_url.is_empty() {
return Err(anyhow::anyhow!("api_url cannot be empty"));
}
if self.api_token.is_empty() {
return Err(anyhow::anyhow!("api_token cannot be empty"));
}
if self.sensor_ref.is_empty() {
return Err(anyhow::anyhow!("sensor_ref cannot be empty"));
}
if self.mq_url.is_empty() {
return Err(anyhow::anyhow!("mq_url cannot be empty"));
}
if self.mq_exchange.is_empty() {
return Err(anyhow::anyhow!("mq_exchange cannot be empty"));
}
// Validate API URL format
if !self.api_url.starts_with("http://") && !self.api_url.starts_with("https://") {
return Err(anyhow::anyhow!(
"api_url must start with http:// or https://"
));
}
// Validate MQ URL format
if !self.mq_url.starts_with("amqp://") && !self.mq_url.starts_with("amqps://") {
return Err(anyhow::anyhow!(
"mq_url must start with amqp:// or amqps://"
));
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_config_validation() {
let config = SensorConfig {
api_url: "http://localhost:8080".to_string(),
api_token: "test_token".to_string(),
sensor_ref: "core.timer".to_string(),
mq_url: "amqp://localhost:5672".to_string(),
mq_exchange: "attune".to_string(),
log_level: "info".to_string(),
};
assert!(config.validate().is_ok());
}
#[test]
fn test_config_validation_invalid_api_url() {
let config = SensorConfig {
api_url: "localhost:8080".to_string(), // Missing http://
api_token: "test_token".to_string(),
sensor_ref: "core.timer".to_string(),
mq_url: "amqp://localhost:5672".to_string(),
mq_exchange: "attune".to_string(),
log_level: "info".to_string(),
};
assert!(config.validate().is_err());
}
#[test]
fn test_config_validation_invalid_mq_url() {
let config = SensorConfig {
api_url: "http://localhost:8080".to_string(),
api_token: "test_token".to_string(),
sensor_ref: "core.timer".to_string(),
mq_url: "localhost:5672".to_string(), // Missing amqp://
mq_exchange: "attune".to_string(),
log_level: "info".to_string(),
};
assert!(config.validate().is_err());
}
#[test]
fn test_config_deserialization() {
let json = r#"{
"api_url": "http://localhost:8080",
"api_token": "test_token",
"sensor_ref": "core.timer",
"mq_url": "amqp://localhost:5672"
}"#;
let config: SensorConfig = serde_json::from_str(json).unwrap();
assert_eq!(config.api_url, "http://localhost:8080");
assert_eq!(config.api_token, "test_token");
assert_eq!(config.sensor_ref, "core.timer");
assert_eq!(config.mq_url, "amqp://localhost:5672");
assert_eq!(config.mq_exchange, "attune"); // Default
assert_eq!(config.log_level, "info"); // Default
}
#[test]
fn test_config_deserialization_with_optionals() {
let json = r#"{
"api_url": "http://localhost:8080",
"api_token": "test_token",
"sensor_ref": "core.timer",
"mq_url": "amqp://localhost:5672",
"mq_exchange": "custom",
"log_level": "debug"
}"#;
let config: SensorConfig = serde_json::from_str(json).unwrap();
assert_eq!(config.mq_exchange, "custom");
assert_eq!(config.log_level, "debug");
}
}