cratery 1.11.1

Cratery -- a private cargo registry
/*******************************************************************************
 * Copyright (c) 2024 Cénotélie Opérations SAS (cenotelie.fr)
******************************************************************************/

//! Utility APIs for async programming

use std::path::Path;
use std::process::Stdio;
use std::time::{Duration, Instant};

use apierror::{error_backend_failure, specialize, ApiError};
use futures::future::BoxFuture;
use tokio::io::AsyncWriteExt;
use tokio::process::Command;

pub mod apierror;
pub mod axum;
pub mod concurrent;
pub mod db;
pub mod hashes;
pub mod shared;
pub mod sigterm;
pub mod token;

/// Pushes an element in a vector if it is not present yet
/// Returns `true` if the vector was modified
pub fn push_if_not_present<T>(v: &mut Vec<T>, item: T) -> bool
where
    T: PartialEq<T>,
{
    if v.contains(&item) {
        false
    } else {
        v.push(item);
        true
    }
}

/// Builds an instant for stale data
/// The value is 7 days before now
#[must_use]
pub fn stale_instant() -> Instant {
    let now = Instant::now();
    now.checked_sub(Duration::from_secs(60 * 60 * 24 * 7)).unwrap()
}

/// Execute a git command
pub async fn execute_git(location: &Path, args: &[&str]) -> Result<(), ApiError> {
    execute_at_location(location, "git", args, &[]).await.map(|_| ())
}

/// Execute a command at a location
pub async fn execute_at_location(location: &Path, command: &str, args: &[&str], input: &[u8]) -> Result<Vec<u8>, ApiError> {
    let mut child = Command::new(command)
        .current_dir(location)
        .args(args)
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .spawn()?;
    child.stdin.as_mut().unwrap().write_all(input).await?;
    let output = child.wait_with_output().await?;
    if output.status.success() {
        Ok(output.stdout)
    } else {
        let stdout = String::from_utf8_lossy(&output.stdout);
        let stderr = String::from_utf8_lossy(&output.stderr);
        let error = format!("-- stdout\n{stdout}\n\n-- stderr\n{stderr}");
        Err(specialize(error_backend_failure(), error))
    }
}

/// Box future that outputs a `Result` with an `ApiError`
pub type FaillibleFuture<'a, T> = BoxFuture<'a, Result<T, ApiError>>;

/// Transforms a comma separated list to a `Vec` of owned `String`
#[must_use]
pub fn comma_sep_to_vec(input: &str) -> Vec<String> {
    input
        .split(',')
        .filter_map(|s| {
            let s = s.trim();
            if s.is_empty() {
                None
            } else {
                Some(s.to_string())
            }
        })
        .collect::<Vec<_>>()
}