Skip to main content

braze_sync/cli/
export.rs

1//! `braze-sync export` — pull current state from Braze into local files.
2//!
3//! v0.1.0 supports Catalog Schema. The other resource kinds produce a
4//! "not yet implemented" warning when selected.
5
6use crate::braze::error::BrazeApiError;
7use crate::braze::BrazeClient;
8use crate::config::ResolvedConfig;
9use crate::fs::catalog_io;
10use crate::resource::ResourceKind;
11use anyhow::Context as _;
12use clap::Args;
13use std::path::Path;
14
15use super::{selected_kinds, warn_unimplemented};
16
17#[derive(Args, Debug)]
18pub struct ExportArgs {
19    /// Limit export to a specific resource kind. Omit to export every
20    /// enabled resource kind in turn.
21    #[arg(long, value_enum)]
22    pub resource: Option<ResourceKind>,
23
24    /// When `--resource` is given, optionally restrict to a single named
25    /// resource. Requires `--resource`.
26    #[arg(long, requires = "resource")]
27    pub name: Option<String>,
28}
29
30pub async fn run(
31    args: &ExportArgs,
32    resolved: ResolvedConfig,
33    config_dir: &Path,
34) -> anyhow::Result<()> {
35    let catalogs_root = config_dir.join(&resolved.resources.catalog_schema.path);
36    let client = BrazeClient::from_resolved(&resolved);
37    let kinds = selected_kinds(args.resource, &resolved.resources);
38
39    let mut total_written: usize = 0;
40    for kind in kinds {
41        match kind {
42            ResourceKind::CatalogSchema => {
43                let n = export_catalog_schemas(&client, &catalogs_root, args.name.as_deref())
44                    .await
45                    .context("exporting catalog_schema")?;
46                eprintln!("✓ catalog_schema: exported {n} resource(s)");
47                total_written += n;
48            }
49            other => {
50                warn_unimplemented(other);
51            }
52        }
53    }
54
55    eprintln!("done: {total_written} resource(s) written");
56    Ok(())
57}
58
59async fn export_catalog_schemas(
60    client: &BrazeClient,
61    catalogs_root: &Path,
62    name_filter: Option<&str>,
63) -> anyhow::Result<usize> {
64    let catalogs = match name_filter {
65        Some(name) => match client.get_catalog(name).await {
66            Ok(c) => vec![c],
67            // get_catalog NotFound is informational, not a hard error —
68            // export of a missing name simply writes nothing.
69            Err(BrazeApiError::NotFound { .. }) => {
70                eprintln!("⚠ catalog_schema: '{name}' not found in Braze");
71                Vec::new()
72            }
73            Err(e) => return Err(e.into()),
74        },
75        None => client.list_catalogs().await?,
76    };
77
78    let count = catalogs.len();
79    for cat in catalogs {
80        catalog_io::save_schema(catalogs_root, &cat)?;
81    }
82    Ok(count)
83}