adnv 1.5.0

CLI that tracks, checks and auto-updates infrastructure dependency versions across Terraform, Helm, Docker, Git and npm
Documentation
use super::{ListOptions, Tag};
use crate::{Error, Result};

#[derive(Debug, serde::Serialize)]
struct Params {
    page: u32,
    per_page: u32,
    state: State,
}

#[derive(Debug, serde::Serialize)]
#[serde(rename_all = "lowercase")]
enum State {
    Active,
    #[allow(dead_code)]
    Deleted,
}

#[derive(Debug, serde::Deserialize)]
struct Entry {
    name: String,
    created_at: chrono::DateTime<chrono::Utc>,
    metadata: Metadata,
}

#[derive(Debug, serde::Deserialize)]
struct Metadata {
    container: ContainerMetadata,
}

#[derive(Debug, serde::Deserialize)]
struct ContainerMetadata {
    tags: Vec<String>,
}

pub async fn fetch(source: &url::Url, options: &ListOptions<'_>) -> Result<Vec<Tag>> {
    let Some((org, name)) = source.path().trim_start_matches('/').split_once('/') else {
        return Err(Error::invalid_source(
            source.clone(),
            "Invalid path (expected format: <ORG>/<NAME>)",
        ));
    };

    let client = crate::util::http::github()?;

    let mut params = Params {
        page: 1,
        per_page: 100,
        state: State::Active,
    };

    let route = format!("/orgs/{org}/packages/container/{name}/versions");

    let mut tags = Vec::new();

    let mut page_number = 0usize;

    'tags: loop {
        page_number += 1;
        if page_number > super::super::MAX_PAGES {
            return Err(super::super::too_many_pages("GitHub Packages"));
        }

        let data: Vec<Entry> = client
            .get(&route, Some(&params))
            .await
            .map_err(|err| Error::github(err, "Could not fetch packages page"))?;

        if data.is_empty() {
            break;
        }

        let entries = data.iter().flat_map(|entry| {
            entry
                .metadata
                .container
                .tags
                .iter()
                .map(move |tag| (entry, tag))
        });
        for (entry, name) in entries {
            use crate::util::iter::FilterWhileAction;

            match options.consider(&entry.name, &entry.created_at) {
                FilterWhileAction::Pick => tags.push(Tag::new(name, entry.created_at)),
                FilterWhileAction::Skip => {}
                FilterWhileAction::Break => break 'tags,
            }
        }

        params.page += 1;
    }

    Ok(tags)
}