inapt 0.1.5

A minimal Debian/Ubuntu APT repository proxy written in Rust. Exposes a valid APT repo structure over HTTP, sourcing .deb packages from GitHub Releases.
Documentation
use std::{borrow::Cow, marker::PhantomData, sync::Arc};

use anyhow::Context;

use crate::{adapter_deb::DebReader, domain::AptRepositoryService};

mod adapter_deb;
mod adapter_github;
mod adapter_http_server;
mod adapter_storage;
mod adapter_worker;
mod domain;

pub mod tracing;

fn maybe_env(name: &str) -> Option<String> {
    std::env::var(name).ok()
}

fn with_env_or<T>(name: &str, default_value: T) -> Cow<'static, str>
where
    T: Into<Cow<'static, str>>,
{
    std::env::var(name)
        .ok()
        .map(Cow::Owned)
        .unwrap_or_else(|| default_value.into())
}

fn with_env_as_or<T>(name: &str, default_value: T) -> anyhow::Result<T>
where
    T: std::str::FromStr,
    <T as std::str::FromStr>::Err: std::error::Error + Send + Sync + 'static,
{
    let Ok(value) = std::env::var(name) else {
        return Ok(default_value);
    };
    value
        .parse::<T>()
        .with_context(|| format!("unable to parse value from {name:?}"))
}

fn with_env_as_many(name: &str, delim: &str) -> Vec<String> {
    let Ok(value) = std::env::var(name) else {
        ::tracing::warn!("no repository configured");
        return Vec::default();
    };
    value
        .split(delim)
        .map(|item| item.trim().to_string())
        .collect()
}

pub struct Config {
    config: domain::Config,
    github: adapter_github::Config,
    http_server: adapter_http_server::Config,
    storage: adapter_storage::Config,
    worker: adapter_worker::Config,
}

impl Config {
    pub fn from_env() -> anyhow::Result<Config> {
        Ok(Self {
            config: domain::Config::from_env()?,
            github: adapter_github::Config::from_env()?,
            http_server: adapter_http_server::Config::from_env()?,
            storage: adapter_storage::Config::from_env()?,
            worker: adapter_worker::Config::from_env()?,
        })
    }

    pub fn build(self) -> anyhow::Result<Application> {
        let release_storage = self.storage.build()?;
        let github = self.github.build()?;
        let apt_repository_service = AptRepositoryService {
            package_source: github.clone(),
            release_storage,
            config: Arc::from(self.config),
            clock: PhantomData::<chrono::Utc>,
            deb_extractor: DebReader,
        };
        Ok(Application {
            github,
            http_server: self
                .http_server
                .builder()?
                .with_apt_repository(apt_repository_service.clone())
                .build()?,
            worker: self
                .worker
                .builder()
                .with_apt_repository(apt_repository_service)
                .build()?,
        })
    }
}

pub struct Application {
    #[allow(unused, reason = "preparation")]
    github: adapter_github::Client,
    http_server: adapter_http_server::Server,
    worker: adapter_worker::Worker,
}

impl Application {
    pub async fn run(self) -> anyhow::Result<()> {
        self.http_server.run().await?;
        self.worker.shutdown().await
    }
}