inapt 0.3.2

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::collections::VecDeque;

use anyhow::Context;

use crate::adapter_github::entity::{Pagination, Release, Repository};

const PAGE_SIZE: u32 = 10;

impl crate::adapter_github::Client {
    pub(crate) async fn list_releases(
        &self,
        repo: Repository<'_>,
        page: Pagination,
    ) -> anyhow::Result<Vec<Release>> {
        let url = format!(
            "{}/repos/{}/{}/releases",
            self.base_url, repo.owner, repo.name
        );
        let res = self
            .inner
            .get(&url)
            .query(&page)
            .send()
            .await
            .context("unable to request")?;
        res.error_for_status_ref()?;
        res.json().await.context("unable to read response")
    }

    pub(crate) fn stream_releases<'a>(&'a self, repo: Repository<'a>) -> ReleaseStreamer<'a> {
        ReleaseStreamer::new(self, repo)
    }
}

pub(crate) struct ReleaseStreamer<'a> {
    client: &'a crate::adapter_github::Client,
    repo: Repository<'a>,
    page_size: u32,
    page_index: u32,
    has_more: bool,
    cache: VecDeque<Release>,
}

impl<'a> ReleaseStreamer<'a> {
    fn new(client: &'a crate::adapter_github::Client, repo: Repository<'a>) -> Self {
        Self {
            client,
            repo,
            page_size: PAGE_SIZE,
            page_index: 0,
            has_more: true,
            cache: VecDeque::default(),
        }
    }

    async fn fetch_next_page(&mut self) -> anyhow::Result<()> {
        self.page_index += 1;
        let list = self
            .client
            .list_releases(self.repo, Pagination::new(self.page_index, self.page_size))
            .await?;
        self.has_more = !list.is_empty();
        self.cache = list
            .into_iter()
            .filter(|item| !item.draft && !item.prerelease)
            .collect();
        Ok(())
    }

    pub(crate) async fn next(&mut self) -> anyhow::Result<Option<Release>> {
        while self.has_more || !self.cache.is_empty() {
            if let Some(item) = self.cache.pop_front() {
                return Ok(Some(item));
            }
            self.fetch_next_page().await.inspect_err(|err| {
                tracing::error!(error = ?err, "unable to fetch next page");
            })?;
        }
        Ok(None)
    }
}