use std::{
fmt::Display,
process::exit,
sync::{
LockResult,
atomic::{AtomicU8, Ordering},
},
};
use anyhow::{Context, Error, Result, anyhow};
use serde::Deserialize;
use tap::Pipe;
use tracing::{Event, Level, Subscriber};
use tracing_subscriber::{Layer, layer};
use crate::env::is_ci;
static MAX_SEVERITY: AtomicU8 = AtomicU8::new(0);
pub fn has_severity(level: Level) -> bool {
MAX_SEVERITY.load(Ordering::Relaxed) >= level_to_severity(level)
}
#[inline]
pub fn put_severity(level: Level) {
let severity = level_to_severity(level);
MAX_SEVERITY.fetch_max(severity, Ordering::Relaxed);
}
pub struct EventLevelLayer;
impl<S: Subscriber> Layer<S> for EventLevelLayer {
fn on_event(&self, event: &Event<'_>, _ctx: layer::Context<'_, S>) {
put_severity(*event.metadata().level());
}
}
fn level_to_severity(level: Level) -> u8 {
if level <= Level::ERROR {
50
} else if level <= Level::WARN {
40
} else if level <= Level::INFO {
30
} else if level <= Level::DEBUG {
20
} else {
10
}
}
#[derive(clap::ValueEnum, Deserialize, Debug, Default, Clone, Copy)]
#[serde(rename_all = "lowercase")]
pub enum OnWarning {
#[default]
#[serde(rename = "ci")]
#[clap(name = "ci")]
FailInCi,
AlwaysFail,
}
impl OnWarning {
pub fn check(&self) -> Result<()> {
if has_severity(Level::ERROR) {
Err(anyhow!("Preprocessor has errors"))
} else if has_severity(Level::WARN) {
match (self, is_ci()) {
(Self::AlwaysFail, _) => anyhow! { "Treating warnings as errors because the \
`fail-on-warnings` option is set to \"always\"" }
.pipe(Err),
(Self::FailInCi, Some(ci)) => {
anyhow! { "Treating warnings as errors because CI={ci} and the \
`fail-on-warnings` option is set to \"ci\"" }
.pipe(Err)
}
(Self::FailInCi, None) => Ok(()),
}
} else {
Ok(())
}
}
pub fn adjusted<T, E>(&self, result: Result<Result<T, E>, E>) -> Result<Result<T, E>, E> {
match result {
Err(error) => Err(error),
Ok(Err(error)) => match (self, is_ci()) {
(Self::AlwaysFail, _) => Err(error),
(Self::FailInCi, Some(_)) => Err(error),
(Self::FailInCi, None) => Ok(Err(error)),
},
Ok(Ok(result)) => Ok(Ok(result)),
}
}
}
pub trait ExpectFmt {
fn expect_fmt(self);
}
impl ExpectFmt for std::fmt::Result {
#[inline(always)]
fn expect_fmt(self) {
self.expect("string formatting should not fail")
}
}
pub trait ExpectLock<T> {
fn expect_lock(self) -> T;
}
impl<T> ExpectLock<T> for LockResult<T> {
#[inline(always)]
fn expect_lock(self) -> T {
self.expect("lock should not be poisoned")
}
}
pub trait IntoAnyhow<T> {
fn anyhow(self) -> Result<T>;
}
impl<T, E: Into<Error>> IntoAnyhow<T> for Result<T, E> {
#[inline(always)]
fn anyhow(self) -> Result<T> {
self.map_err(Into::into)
}
}
#[allow(async_fn_in_trait)]
pub trait FutureWithError<T> {
async fn context<C>(self, context: C) -> Result<T>
where
C: Display + Send + Sync + 'static;
async fn with_context<C, G>(self, context: G) -> Result<T>
where
C: Display + Send + Sync + 'static,
G: FnOnce() -> C;
}
impl<F, T, E> FutureWithError<T> for F
where
F: Future<Output = Result<T, E>>,
E: Into<Error>,
{
#[inline(always)]
async fn context<C>(self, context: C) -> Result<T>
where
C: Display + Send + Sync + 'static,
{
match self.await {
Ok(value) => Ok(value),
Err(error) => Err(error.into()).context(context),
}
}
#[inline(always)]
async fn with_context<C, G>(self, context: G) -> Result<T>
where
C: Display + Send + Sync + 'static,
G: FnOnce() -> C,
{
match self.await {
Ok(value) => Ok(value),
Err(error) => Err(error.into()).with_context(context),
}
}
}
pub trait ExitProcess<T> {
fn exit(self, log: impl FnOnce(Error)) -> T;
}
impl<T> ExitProcess<T> for Result<T> {
fn exit(self, log: impl FnOnce(Error)) -> T {
match self {
Ok(v) => v,
Err(e) => {
log(e);
exit(1)
}
}
}
}