use std::cmp::Ordering;
use std::path::Path;
use std::time::SystemTime;
use chrono::DateTime;
use chrono::FixedOffset;
use chrono::Utc;
use super::paths;
use super::read_write;
use super::run::LintRun;
use super::run::LintRunStatus;
use crate::config::DiscoveryLint;
use crate::constants::STALE_TIMEOUT;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LintStatusKind {
Running,
Passed,
Failed,
Stale,
NoLog,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub enum LintStatus {
Running(DateTime<FixedOffset>),
Passed(DateTime<FixedOffset>),
Failed(DateTime<FixedOffset>),
Stale,
#[default]
NoLog,
}
impl LintStatus {
pub const fn kind(&self) -> LintStatusKind {
match self {
Self::Running(_) => LintStatusKind::Running,
Self::Passed(_) => LintStatusKind::Passed,
Self::Failed(_) => LintStatusKind::Failed,
Self::Stale => LintStatusKind::Stale,
Self::NoLog => LintStatusKind::NoLog,
}
}
const fn severity_rank(&self) -> u8 {
match self {
Self::NoLog => 0,
Self::Passed(_) => 1,
Self::Stale => 2,
Self::Running(_) => 3,
Self::Failed(_) => 4,
}
}
pub fn combine(self, other: Self) -> Self {
match self.severity_rank().cmp(&other.severity_rank()) {
Ordering::Greater => self,
Ordering::Less => other,
Ordering::Equal => match (self, other) {
(Self::Passed(lhs), Self::Passed(rhs)) => Self::Passed(lhs.max(rhs)),
(Self::Running(lhs), Self::Running(rhs)) => Self::Running(lhs.max(rhs)),
(Self::Failed(lhs), Self::Failed(rhs)) => Self::Failed(lhs.max(rhs)),
(Self::Stale, Self::Stale) => Self::Stale,
(Self::NoLog, Self::NoLog) => Self::NoLog,
(lhs, _) => lhs,
},
}
}
pub fn aggregate<I>(statuses: I) -> Self
where
I: IntoIterator<Item = Self>,
{
statuses
.into_iter()
.reduce(Self::combine)
.unwrap_or(Self::NoLog)
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub enum CachedLintStatus {
Passed(DateTime<FixedOffset>),
Failed(DateTime<FixedOffset>),
#[default]
NoLog,
}
impl CachedLintStatus {
pub const fn from_lint_status(status: &LintStatus) -> Option<Self> {
match status {
LintStatus::Passed(timestamp) => Some(Self::Passed(*timestamp)),
LintStatus::Failed(timestamp) => Some(Self::Failed(*timestamp)),
LintStatus::NoLog => Some(Self::NoLog),
LintStatus::Running(_) | LintStatus::Stale => None,
}
}
pub const fn into_lint_status(self) -> LintStatus {
match self {
Self::Passed(timestamp) => LintStatus::Passed(timestamp),
Self::Failed(timestamp) => LintStatus::Failed(timestamp),
Self::NoLog => LintStatus::NoLog,
}
}
pub fn should_lint_on_startup(
&self,
last_started_at: Option<DateTime<FixedOffset>>,
max_source_mtime: Option<SystemTime>,
on_discovery: DiscoveryLint,
) -> bool {
match self {
Self::NoLog => on_discovery.is_immediate(),
Self::Passed(_) | Self::Failed(_) => match (last_started_at, max_source_mtime) {
(Some(started), Some(mtime)) => source_is_newer(mtime, started),
_ => false,
},
}
}
}
fn source_is_newer(mtime: SystemTime, last_started_at: DateTime<FixedOffset>) -> bool {
let Ok(since_epoch) = mtime.duration_since(SystemTime::UNIX_EPOCH) else {
return false;
};
let mtime_secs = i64::try_from(since_epoch.as_secs()).unwrap_or(i64::MAX);
mtime_secs > last_started_at.timestamp()
}
pub fn read_status_under(cache_root: &Path, project_root: &Path) -> LintStatus {
read_status_from_path(&paths::latest_path_under(cache_root, project_root))
}
fn read_status_from_path(path: &Path) -> LintStatus {
let Some(run) = read_write::read_latest_file(path) else {
return LintStatus::NoLog;
};
parse_run(&run)
}
pub(crate) fn parse_timestamp(value: &str) -> Option<DateTime<FixedOffset>> {
DateTime::parse_from_rfc3339(value.trim()).ok()
}
pub(super) fn parse_run(run: &LintRun) -> LintStatus {
let timestamp = run
.finished_at
.as_deref()
.and_then(parse_timestamp)
.or_else(|| parse_timestamp(&run.started_at));
let Some(ts) = timestamp else {
return LintStatus::NoLog;
};
match run.status {
LintRunStatus::Passed => LintStatus::Passed(ts),
LintRunStatus::Failed => LintStatus::Failed(ts),
LintRunStatus::Running => {
let elapsed = Utc::now().signed_duration_since(ts);
if elapsed > chrono::Duration::from_std(STALE_TIMEOUT).unwrap_or_default() {
LintStatus::Stale
} else {
LintStatus::Running(ts)
}
},
}
}