seex 0.1.0-beta.5

Unified Rust SDK facade for Seex.
use crate::model::metric::{MetricAggregate, MetricKey, Step};
use crate::model::run::{Run, RunId, RunStatus};
use crate::model::types::ProjectId;
use crate::storage::StorageError;
use crate::storage::time::timestamp_from_millis;

pub struct StoredRun {
    pub run_id: String,
    pub project_id: String,
    pub name: String,
    pub status: String,
    pub created_at_millis: i64,
    pub started_at_millis: i64,
    pub finished_at_millis: Option<i64>,
}

impl StoredRun {
    pub fn into_run(self) -> Result<Run, StorageError> {
        Ok(Run {
            run_id: RunId::from_string(self.run_id),
            project_id: ProjectId::from_string(self.project_id),
            name: self.name,
            status: run_status_from_str(&self.status)?,
            created_at: timestamp_from_millis("created_at", self.created_at_millis)?,
            started_at: timestamp_from_millis("started_at", self.started_at_millis)?,
            finished_at: self
                .finished_at_millis
                .map(|millis| timestamp_from_millis("finished_at", millis))
                .transpose()?,
        })
    }
}

pub struct StoredMetricAggregate {
    pub run_id: String,
    pub metric_key: String,
    pub effective_count: u64,
    pub last_step: i64,
    pub last_value_f64: f64,
    pub min_value_f64: f64,
    pub max_value_f64: f64,
}

impl StoredMetricAggregate {
    pub fn into_metric_aggregate(self) -> MetricAggregate {
        MetricAggregate {
            run_id: RunId::from_string(self.run_id),
            metric_key: MetricKey::from_string(self.metric_key),
            effective_count: self.effective_count,
            last_step: Step::new(self.last_step),
            last_value_f64: self.last_value_f64,
            min_value_f64: self.min_value_f64,
            max_value_f64: self.max_value_f64,
        }
    }
}

pub const fn status_as_str(status: RunStatus) -> &'static str {
    match status {
        RunStatus::Running => "running",
        RunStatus::Finished => "finished",
        RunStatus::Failed => "failed",
    }
}

fn run_status_from_str(status: &str) -> Result<RunStatus, StorageError> {
    match status {
        "running" => Ok(RunStatus::Running),
        "finished" => Ok(RunStatus::Finished),
        "failed" => Ok(RunStatus::Failed),
        _ => Err(StorageError::InvalidRunStatus {
            status: status.to_owned(),
        }),
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn run_status_round_trips_storage_values() {
        let statuses = [
            (RunStatus::Running, "running"),
            (RunStatus::Finished, "finished"),
            (RunStatus::Failed, "failed"),
        ];

        for (status, raw) in statuses {
            assert_eq!(status_as_str(status), raw);
            assert_eq!(run_status_from_str(raw).unwrap(), status);
        }
    }

    #[test]
    fn run_status_from_str_rejects_unknown_storage_value() {
        let err = run_status_from_str("paused").unwrap_err();

        assert!(
            matches!(
                err,
                StorageError::InvalidRunStatus { ref status } if status == "paused"
            ),
            "expected invalid run status error, got {err:?}",
        );
    }
}