use crate::{config::IndexerConfig, defaults};
use anyhow::Result;
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use std::{
env,
fs::canonicalize,
future::Future,
net::{SocketAddr, ToSocketAddrs},
path::Path,
str::FromStr,
};
use tokio::time::{sleep, Duration};
use tracing::{debug, info, warn};
use tracing_subscriber::filter::EnvFilter;
const RUST_LOG: &str = "RUST_LOG";
const HUMAN_LOGGING: &str = "HUMAN_LOGGING";
const ROOT_DIRECTORY_NAME: &str = "fuel-indexer";
pub fn serialize(obj: &impl Serialize) -> Vec<u8> {
bincode::serialize(obj).expect("Serialize failed")
}
pub fn deserialize<'a, T: Deserialize<'a>>(bytes: &'a [u8]) -> Result<T, String> {
match bincode::deserialize(bytes) {
Ok(obj) => Ok(obj),
Err(e) => Err(format!("Bincode serde error {e:?}")),
}
}
pub fn local_repository_root() -> Option<String> {
let curr_filepath = canonicalize(file!()).unwrap();
let mut curr_dir = Path::new(&curr_filepath);
let mut depth = 4;
while depth > 0 {
match curr_dir.parent() {
Some(p) => {
curr_dir = p;
depth -= 1;
}
None => {
return None;
}
}
}
if !curr_dir.is_dir() || curr_dir.file_name().unwrap() != ROOT_DIRECTORY_NAME {
return None;
}
let root_dir = curr_dir.as_os_str().to_str().unwrap().to_string();
Some(root_dir)
}
#[derive(Debug)]
pub struct ReloadRequest {
pub namespace: String,
pub identifier: String,
}
#[derive(Debug)]
pub struct StopRequest {
pub namespace: String,
pub identifier: String,
}
#[derive(Debug)]
pub enum ServiceRequest {
Reload(ReloadRequest),
Stop(StopRequest),
}
pub fn sha256_digest<T: AsRef<[u8]>>(b: &T) -> String {
let mut hasher = Sha256::new();
hasher.update(b);
format!("{:x}", hasher.finalize())
}
pub fn trim_opt_env_key(key: &str) -> &str {
let not_ambiguous = key.starts_with("${");
match not_ambiguous {
false => &key[1..],
true => &key[2..key.len() - 1],
}
}
pub fn is_opt_env_var(k: &str) -> bool {
k.starts_with('$') || (k.starts_with("${") && k.ends_with('}'))
}
pub fn derive_socket_addr(host: &str, port: &str) -> SocketAddr {
let host = format!("{host}:{port}");
match SocketAddr::from_str(&host) {
Ok(v) => v,
Err(e) => {
debug!("Failed to parse '{host}': {e}. Retrying...");
let mut addrs: Vec<_> = host
.to_socket_addrs()
.unwrap_or_else(|e| panic!("Unable to resolve domain: {e}"))
.collect();
let addr = addrs.pop().expect("Could not derive SocketAddr from '{}'");
info!("Parsed SocketAddr '{addr:?}' from '{host}'");
addr
}
}
}
pub async fn attempt_database_connection<F, Fut, T, U>(mut fut: F) -> T
where
F: FnMut() -> Fut,
Fut: Future<Output = Result<T, U>>,
U: std::error::Error,
{
let mut remaining_retries = defaults::MAX_DATABASE_CONNECTION_ATTEMPTS;
let mut delay = defaults::INITIAL_RETRY_DELAY_SECS;
loop {
match fut().await {
Ok(t) => break t,
Err(_) => {
if remaining_retries > 0 {
warn!(
"Could not connect to database. Retrying in {delay} seconds...",
);
remaining_retries -= 1;
sleep(Duration::from_secs(delay)).await;
delay *= 2;
} else {
panic!("Retry attempts exceeded. Could not connect to database!")
}
}
}
}
}
#[derive(Debug, Serialize, Deserialize)]
pub enum ServiceStatus {
OK,
NotOk,
}
impl From<FuelClientHealthResponse> for ServiceStatus {
fn from(r: FuelClientHealthResponse) -> Self {
match r.up {
true => ServiceStatus::OK,
_ => ServiceStatus::NotOk,
}
}
}
#[derive(Serialize, Deserialize, Default, Debug)]
pub struct FuelClientHealthResponse {
up: bool,
}
pub async fn init_logging(config: &IndexerConfig) -> anyhow::Result<()> {
let level = env::var_os(RUST_LOG)
.map(|x| x.into_string().unwrap())
.unwrap_or("info".to_string());
if !config.verbose {
std::env::set_var(
RUST_LOG,
format!("{level},wasmer_compiler_cranelift=warn,regalloc=warn,cranelift_codegen=warn"),
);
}
let filter = match env::var_os(RUST_LOG) {
Some(_) => {
EnvFilter::try_from_default_env().expect("Invalid `RUST_LOG` provided")
}
None => EnvFilter::new("info"),
};
let human_logging = env::var_os(HUMAN_LOGGING)
.map(|s| {
bool::from_str(s.to_str().unwrap())
.expect("Expected `true` or `false` to be provided for `HUMAN_LOGGING`")
})
.unwrap_or(true);
let sub = tracing_subscriber::fmt::Subscriber::builder()
.with_writer(std::io::stderr)
.with_env_filter(filter);
if human_logging {
sub.with_ansi(true)
.with_level(true)
.with_line_number(true)
.init();
} else {
sub.with_ansi(false)
.with_level(true)
.with_line_number(true)
.json()
.init();
}
Ok(())
}