use super::{Invocation, walk_options};
use crate::clients::WALK_BUDGET;
use crate::error::CliError;
use crate::output::Format;
use crate::pagination::{self, GetParams, ListParams};
use crate::views;
use scm::airs::management::models::CustomTopic;
use scm::airs::management::{TopicListAllOptions, TopicsClient};
use scm::pagination::{CollectOptions, CollectionError};
use scm::{ErrorKind, RequestOptions};
const LATEST_PAGE: u32 = 200;
pub async fn list(
invocation: &mut Invocation,
params: &ListParams,
format: Option<Format>,
) -> Result<(), CliError> {
let options = walk_options()?;
let (management, format) = invocation
.connect_management(format, Some(&options))
.await?;
let topics = management.topics();
let listed = match (params.all_versions, params.all) {
(false, false) => latest_page(&topics, params, &options).await,
(false, true) => latest_all(&topics, params, &options).await,
(true, false) => versions_page(&topics, params, &options).await,
(true, true) => versions_all(&topics, params, &options).await,
};
let (rows, status) =
listed.map_err(|error| invocation.management_error(&error, WALK_BUDGET))?;
invocation.output.status(&status);
views::render_topic_list(&mut invocation.output, &rows, format)
}
async fn latest_page(
topics: &TopicsClient,
params: &ListParams,
options: &RequestOptions,
) -> Result<(Vec<CustomTopic>, String), scm::Error> {
let rows = complete_walk(
topics,
LATEST_PAGE,
pagination::collect_options(0)?,
options,
)
.await?;
let latest = views::latest_per_name(rows);
let total = latest.len() as u64;
let more = params.offset.saturating_add(u64::from(params.limit)) < total;
let rows = pagination::slice(latest, params.offset, params.limit);
let status = pagination::showing_page(rows.len(), more);
Ok((rows, status))
}
async fn latest_all(
topics: &TopicsClient,
params: &ListParams,
options: &RequestOptions,
) -> Result<(Vec<CustomTopic>, String), scm::Error> {
let rows = complete_walk(
topics,
LATEST_PAGE,
pagination::collect_options(0)?,
options,
)
.await?;
let (rows, end) = pagination::first(views::latest_per_name(rows), params.max);
let status = pagination::all_status(rows.len(), end, params.max);
Ok((rows, status))
}
async fn versions_page(
topics: &TopicsClient,
params: &ListParams,
options: &RequestOptions,
) -> Result<(Vec<CustomTopic>, String), scm::Error> {
let rows = complete_walk(topics, 100, pagination::collect_options(0)?, options).await?;
let total = rows.len();
let rows = pagination::slice(rows, params.offset, params.limit);
let status = pagination::showing_slice(rows.len(), total, params.offset, params.limit);
Ok((rows, status))
}
async fn versions_all(
topics: &TopicsClient,
params: &ListParams,
options: &RequestOptions,
) -> Result<(Vec<CustomTopic>, String), scm::Error> {
let mut all = TopicListAllOptions::default();
all.collect = pagination::collect_options(params.max)?;
let collection = topics
.list_all(all, Some(options))
.await
.map_err(CollectionError::into_error)?;
let end = pagination::walk_end(collection.completion, params.max)
.ok_or_else(pagination::incomplete_walk)?;
let status = pagination::all_status(collection.items.len(), end, params.max);
Ok((collection.items, status))
}
pub(crate) async fn complete_walk(
topics: &TopicsClient,
limit: u32,
collect: CollectOptions,
options: &RequestOptions,
) -> Result<Vec<CustomTopic>, scm::Error> {
let mut all = TopicListAllOptions::default();
all.limit = limit;
all.collect = collect;
let collection = topics
.list_all(all, Some(options))
.await
.map_err(CollectionError::into_error)?;
if collection.is_complete() {
Ok(collection.items)
} else {
Err(pagination::incomplete_walk())
}
}
pub async fn get(
invocation: &mut Invocation,
params: &GetParams,
format: Option<Format>,
) -> Result<(), CliError> {
let options = walk_options()?;
let (management, format) = invocation
.connect_management(format, Some(&options))
.await?;
let topics = management.topics();
let value = params.name_or_id.as_str();
let by_id = params.is_id();
let filtered = params.revision.is_some() || params.all_versions;
if !filtered {
let found = if by_id {
topics.get(value, Some(&options)).await
} else {
topics.get_by_name(value, Some(&options)).await
};
return match found {
Ok(topic) => views::render_topic_detail(&mut invocation.output, &topic, format),
Err(error) if error.kind() == ErrorKind::Validation => {
Err(CliError::operational(not_found(value, by_id, false)))
}
Err(error) => Err(invocation.management_error(&error, WALK_BUDGET)),
};
}
let rows = complete_walk(&topics, 100, CollectOptions::default(), &options)
.await
.map_err(|error| invocation.management_error(&error, WALK_BUDGET))?;
let selected = views::matching_versions(rows, value, by_id, params.revision);
let Some(first) = selected.first() else {
return Err(CliError::operational(not_found(value, by_id, true)));
};
if params.all_versions {
views::render_topic_list(&mut invocation.output, &selected, format)
} else {
views::render_topic_detail(&mut invocation.output, first, format)
}
}
fn not_found(value: &str, by_id: bool, filtered: bool) -> String {
if by_id || filtered {
format!("Topic {value} not found")
} else {
format!("Topic \"{value}\" not found")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn not_found_messages() {
let id = "00000000-0000-4000-8000-000000000011";
assert_eq!(not_found(id, true, false), format!("Topic {id} not found"));
assert_eq!(not_found("fin", false, false), "Topic \"fin\" not found");
assert_eq!(not_found("fin", false, true), "Topic fin not found");
assert_eq!(not_found(id, true, true), format!("Topic {id} not found"));
}
}