#![deny(missing_docs)]
#![deny(clippy::all, clippy::pedantic)]
#![deny(
clippy::allow_attributes_without_reason,
clippy::assertions_on_result_states,
clippy::dbg_macro,
clippy::decimal_literal_representation,
clippy::exhaustive_enums,
clippy::exhaustive_structs,
clippy::iter_over_hash_type,
clippy::let_underscore_must_use,
clippy::missing_assert_message,
clippy::print_stderr,
clippy::print_stdout,
clippy::undocumented_unsafe_blocks,
clippy::unnecessary_safety_comment,
clippy::unwrap_used
)]
#![allow(clippy::needless_pass_by_value, reason = "Needed for axum")]
use core::fmt;
use std::sync::{
Arc, Mutex,
atomic::{AtomicBool, Ordering},
};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use tokio::signal;
use tokio_util::sync::CancellationToken;
pub use git_version;
#[cfg(feature = "api")]
pub mod api;
#[cfg(feature = "postgres")]
pub mod postgres;
#[cfg(feature = "alloy")]
pub mod web3;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[allow(
clippy::exhaustive_enums,
reason = "We only expect those four environments at the moment. Changing that is a breaking change."
)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
pub enum Environment {
Prod,
Stage,
Test,
Dev,
}
impl fmt::Display for Environment {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let str = match self {
Environment::Prod => "prod",
Environment::Stage => "stage",
Environment::Test => "test",
Environment::Dev => "dev",
};
f.write_str(str)
}
}
impl Environment {
pub fn assert_is_dev(&self) {
assert!(self.is_dev(), "Is not dev environment");
}
#[must_use]
pub fn is_dev(&self) -> bool {
matches!(self, Environment::Dev)
}
#[must_use]
pub fn is_not_dev(&self) -> bool {
!self.is_dev()
}
}
#[macro_export]
macro_rules! version_info {
() => {
format!(
"{} {} ({})",
env!("CARGO_PKG_NAME"),
env!("CARGO_PKG_VERSION"),
option_env!("GIT_HASH")
.unwrap_or($crate::git_version::git_version!(fallback = "UNKNOWN"))
)
};
}
#[derive(Debug, Clone, Default)]
pub struct StartedServices {
external_service: Arc<Mutex<Vec<Arc<AtomicBool>>>>,
}
impl StartedServices {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
#[allow(clippy::missing_panics_doc, reason = "Ok to panic for lock poisoning")]
pub fn new_service(&self) -> Arc<AtomicBool> {
let service = Arc::new(AtomicBool::default());
self.external_service
.lock()
.expect("Not poisoned")
.push(Arc::clone(&service));
service
}
#[must_use]
#[allow(clippy::missing_panics_doc, reason = "Ok to panic for lock poisoning")]
pub fn all_started(&self) -> bool {
self.external_service
.lock()
.expect("Not poisoned")
.iter()
.all(|service| service.load(Ordering::Relaxed))
}
}
pub fn spawn_shutdown_task(
shutdown_signal: impl Future<Output = ()> + Send + 'static,
) -> (CancellationToken, Arc<AtomicBool>) {
let cancellation_token = CancellationToken::new();
let is_graceful = Arc::new(AtomicBool::new(false));
let task_token = cancellation_token.clone();
tokio::task::spawn({
let is_graceful = Arc::clone(&is_graceful);
async move {
let _drop_guard = task_token.drop_guard_ref();
tokio::select! {
() = shutdown_signal => {
tracing::info!("received graceful shutdown");
is_graceful.store(true, Ordering::Relaxed);
task_token.cancel();
}
() = task_token.cancelled() => {}
}
}
});
(cancellation_token, is_graceful)
}
pub async fn default_shutdown_signal() {
let ctrl_c = async {
signal::ctrl_c()
.await
.expect("failed to install Ctrl+C handler");
};
#[cfg(unix)]
let terminate = async {
signal::unix::signal(signal::unix::SignalKind::terminate())
.expect("failed to install signal handler")
.recv()
.await;
};
#[cfg(not(unix))]
let terminate = std::future::pending::<()>();
tokio::select! {
() = ctrl_c => {},
() = terminate => {},
}
}
#[cfg(feature = "aws")]
pub async fn localstack_aws_config() -> aws_config::SdkConfig {
use aws_config::Region;
use aws_sdk_secretsmanager::config::Credentials;
let region_provider = Region::new("us-east-1");
let credentials = Credentials::new("test", "test", None, None, "Static");
aws_config::from_env()
.region(region_provider)
.endpoint_url(
std::env::var("TEST_AWS_ENDPOINT_URL").unwrap_or("http://localhost:4566".to_string()),
)
.credentials_provider(credentials)
.load()
.await
}