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(¶ms))
.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)
}