use crate::braze::error::BrazeApiError;
use crate::braze::BrazeClient;
use crate::config::ResolvedConfig;
use crate::diff::catalog::diff_schema;
use crate::diff::{DiffSummary, ResourceDiff};
use crate::error::Error;
use crate::format::OutputFormat;
use crate::fs::catalog_io;
use crate::resource::{Catalog, ResourceKind};
use anyhow::Context as _;
use clap::Args;
use std::collections::{BTreeMap, BTreeSet};
use std::path::Path;
use super::{selected_kinds, warn_unimplemented};
#[derive(Args, Debug)]
pub struct DiffArgs {
#[arg(long, value_enum)]
pub resource: Option<ResourceKind>,
#[arg(long, requires = "resource")]
pub name: Option<String>,
#[arg(long)]
pub fail_on_drift: bool,
}
pub async fn run(
args: &DiffArgs,
resolved: ResolvedConfig,
config_dir: &Path,
format: OutputFormat,
) -> anyhow::Result<()> {
let catalogs_root = config_dir.join(&resolved.resources.catalog_schema.path);
let client = BrazeClient::from_resolved(&resolved);
let kinds = selected_kinds(args.resource, &resolved.resources);
let mut summary = DiffSummary::default();
for kind in kinds {
match kind {
ResourceKind::CatalogSchema => {
let diffs =
compute_catalog_schema_diffs(&client, &catalogs_root, args.name.as_deref())
.await
.context("computing catalog_schema diff")?;
summary.diffs.extend(diffs);
}
other => {
warn_unimplemented(other);
}
}
}
let formatted = format.formatter().format(&summary);
print!("{formatted}");
if args.fail_on_drift && summary.changed_count() > 0 {
return Err(Error::DriftDetected {
count: summary.changed_count(),
}
.into());
}
Ok(())
}
pub(crate) async fn compute_catalog_schema_diffs(
client: &BrazeClient,
catalogs_root: &Path,
name_filter: Option<&str>,
) -> anyhow::Result<Vec<ResourceDiff>> {
let mut local = catalog_io::load_all_schemas(catalogs_root)?;
if let Some(name) = name_filter {
local.retain(|c| c.name == name);
}
let remote: Vec<Catalog> = match name_filter {
Some(name) => match client.get_catalog(name).await {
Ok(c) => vec![c],
Err(BrazeApiError::NotFound { .. }) => Vec::new(),
Err(e) => return Err(e.into()),
},
None => client.list_catalogs().await?,
};
let local_by_name: BTreeMap<&str, &Catalog> =
local.iter().map(|c| (c.name.as_str(), c)).collect();
let remote_by_name: BTreeMap<&str, &Catalog> =
remote.iter().map(|c| (c.name.as_str(), c)).collect();
let mut all_names: BTreeSet<&str> = BTreeSet::new();
all_names.extend(local_by_name.keys().copied());
all_names.extend(remote_by_name.keys().copied());
let mut diffs = Vec::new();
for name in all_names {
let l = local_by_name.get(name).copied();
let r = remote_by_name.get(name).copied();
if let Some(d) = diff_schema(l, r) {
diffs.push(ResourceDiff::CatalogSchema(d));
}
}
Ok(diffs)
}