use std::sync::Arc;
use async_trait::async_trait;
use operon::options::{OperonOptions, PsqlMetaStorageOptions, PsqlStorageOptions};
use operon::{Operon, OperonService, PsqlMetaStorage, define_operon};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
struct Sensor {
name: String,
readings: Vec<f64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct Summary {
sensor_name: String,
mean: f64,
min: f64,
max: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct Report(String);
define_operon! {
sensors = {
Sensor<sensor_id> = collect();
Summary<batch_id> = aggregate(Sensor) for sensor_id;
Report = report(Summary) for sensor_id, batch_id;
}
}
#[derive(OperonService)]
struct SensorService;
#[async_trait]
impl SensorsService for SensorService {
async fn collect(&self) -> Result<Vec<Sensor>, Self::Error> {
tracing::info!("Collecting sensor data");
Ok(vec![
Sensor {
name: "temperature".into(),
readings: vec![20.1, 21.3, 19.8, 22.0, 20.5],
},
Sensor {
name: "humidity".into(),
readings: vec![45.0, 47.2, 44.8, 46.1, 45.5],
},
Sensor {
name: "pressure".into(),
readings: vec![1013.0, 1012.5, 1013.2, 1012.8, 1013.1],
},
])
}
async fn aggregate(&self, sensor: Sensor) -> Result<Vec<Summary>, Self::Error> {
let span = tracing::info_span!("aggregate", sensor = %sensor.name);
let _guard = span.enter();
log::info!("(log crate) aggregating sensor: {}", sensor.name);
println!("(println) processing {} readings", sensor.readings.len());
eprintln!("(eprintln) sensor range check for {}", sensor.name);
let sum: f64 = sensor.readings.iter().sum();
let mean = sum / sensor.readings.len() as f64;
let min = sensor
.readings
.iter()
.cloned()
.fold(f64::INFINITY, f64::min);
let max = sensor
.readings
.iter()
.cloned()
.fold(f64::NEG_INFINITY, f64::max);
let summary = Summary {
sensor_name: sensor.name.clone(),
mean,
min,
max,
};
Ok(vec![summary.clone(), summary])
}
async fn report(&self, summary: Summary) -> Result<Report, Self::Error> {
tracing::info!(
sensor = %summary.sensor_name,
mean = %format!("{:.1}", summary.mean),
"Generating report"
);
tracing::debug!("Range: {:.1} - {:.1}", summary.min, summary.max);
let text = format!(
"{}: mean={:.1}, range=[{:.1}, {:.1}]",
summary.sensor_name, summary.mean, summary.min, summary.max
);
Ok(Report(text))
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let database_uri = std::env::var("POSTGRES_URI")?;
let operon_options = OperonOptions::new().with_log_dump("./dump");
let service = Arc::new(SensorService);
let storage = Arc::new(
PsqlStorageOptions::new(&database_uri)
.with_schema("ex4_data")
.build::<PsqlSensorsStorage>()?,
);
let meta = PsqlMetaStorageOptions::new(&database_uri)
.with_schema("ex4_meta")
.build()?;
let operon_instance: Operon<SensorService, PsqlSensorsStorage, PsqlMetaStorage> =
Operon::new(service, storage.clone(), meta).with_options(operon_options);
operon_instance.run().await?;
println!("Done! Checking stored reports...");
if let Some(report) = storage.get_report([0, 0]).await? {
println!("Report [0,0]: {}", report.0);
}
Ok(())
}