use super::{Invocation, walk_options};
use crate::clients::{SINGLE_REQUEST_BUDGET, WALK_BUDGET};
use crate::error::CliError;
use crate::output::Format;
use crate::pagination::{self, GetParams, ListParams};
use crate::views;
use scm::ErrorKind;
use scm::airs::management::models::FiniteNumber;
use scm::airs::management::{PaginationOptions, ProfileListAllOptions, ProfilesClient};
use scm::pagination::CollectionError;
pub async fn list(
invocation: &mut Invocation,
params: &ListParams,
format: Option<Format>,
) -> Result<(), CliError> {
if params.all {
return list_all(invocation, params, format).await;
}
let (management, format) = invocation.connect_management(format, None).await?;
let mut page = PaginationOptions::default();
page.offset = params.offset;
page.limit = params.limit;
page.latest = Some(!params.all_versions);
let response = management
.profiles()
.list(page, None)
.await
.map_err(|error| invocation.management_error(&error, SINGLE_REQUEST_BUDGET))?;
let more = response
.next_offset
.as_ref()
.into_option()
.and_then(FiniteNumber::as_f64)
.is_some_and(|next| next > 0.0);
let rows = response.ai_profiles.into_option().unwrap_or_default();
invocation
.output
.status(&pagination::showing_page(rows.len(), more));
views::render_profile_list(&mut invocation.output, &rows, format)
}
async fn list_all(
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 mut all = ProfileListAllOptions::default();
all.limit = params.limit;
all.latest = Some(!params.all_versions);
all.collect = pagination::collect_options(params.max)
.map_err(|error| invocation.management_error(&error, WALK_BUDGET))?;
let collection = management
.profiles()
.list_all(all, Some(&options))
.await
.map_err(|error| invocation.management_error(error.error(), WALK_BUDGET))?;
let end = pagination::walk_end(collection.completion, params.max)
.ok_or_else(|| invocation.management_error(&pagination::incomplete_walk(), WALK_BUDGET))?;
invocation.output.status(&pagination::all_status(
collection.items.len(),
end,
params.max,
));
views::render_profile_list(&mut invocation.output, &collection.items, format)
}
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 profiles = management.profiles();
let value = params.name_or_id.as_str();
let filtered = params.revision.is_some() || params.all_versions;
if !filtered {
let found = if params.is_id() {
profiles.get(value, Some(&options)).await
} else {
profiles.get_by_name(value, Some(&options)).await
};
return match found {
Ok(profile) => views::render_profile_detail(&mut invocation.output, &profile, format),
Err(error) if error.kind() == ErrorKind::Validation => {
Err(CliError::operational(not_found(value, false)))
}
Err(error) => Err(invocation.management_error(&error, WALK_BUDGET)),
};
}
let rows = every_revision(&profiles, &options)
.await
.map_err(|error| invocation.management_error(&error, WALK_BUDGET))?;
let selected = views::matching_versions(rows, value, params.is_id(), params.revision);
let Some(first) = selected.first() else {
return Err(CliError::operational(not_found(value, true)));
};
if params.all_versions {
views::render_profile_list(&mut invocation.output, &selected, format)
} else {
views::render_profile_detail(&mut invocation.output, first, format)
}
}
async fn every_revision(
profiles: &ProfilesClient,
options: &scm::RequestOptions,
) -> Result<Vec<scm::airs::management::models::SecurityProfile>, scm::Error> {
let mut all = ProfileListAllOptions::default();
all.latest = Some(false);
let collection = profiles
.list_all(all, Some(options))
.await
.map_err(CollectionError::into_error)?;
if collection.is_complete() {
Ok(collection.items)
} else {
Err(pagination::incomplete_walk())
}
}
fn not_found(value: &str, filtered: bool) -> String {
if filtered {
format!("Profile {value} not found")
} else {
format!("AISEC_USER_REQUEST_PAYLOAD_ERROR:Profile not found: {value}")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn not_found_messages() {
assert_eq!(
not_found("alpha", false),
"AISEC_USER_REQUEST_PAYLOAD_ERROR:Profile not found: alpha"
);
assert_eq!(
not_found("00000000-0000-4000-8000-000000000002", false),
"AISEC_USER_REQUEST_PAYLOAD_ERROR:Profile not found: 00000000-0000-4000-8000-000000000002"
);
assert_eq!(not_found("alpha", true), "Profile alpha not found");
assert_eq!(
CliError::operational(not_found("alpha", true)).render(),
"\n ✗ Error: Profile alpha not found\n\n"
);
}
}