Skip to main content

braze_sync/config/
schema.rs

1//! Raw configuration types deserialized from `braze-sync.config.yaml`.
2//!
3//! See IMPLEMENTATION.md §10. Every struct here uses
4//! `#[serde(deny_unknown_fields)]` — the config file is the **only** place in
5//! braze-sync where unknown fields are rejected. Resource files
6//! (`schema.yaml`, `template.yaml`, etc.) stay forward-compat permissive
7//! per §2.5.
8
9use serde::Deserialize;
10use std::collections::BTreeMap;
11use std::path::PathBuf;
12use url::Url;
13
14#[derive(Debug, Clone, Deserialize)]
15#[serde(deny_unknown_fields)]
16pub struct ConfigFile {
17    /// Schema version. v1.0 binaries accept exactly `1`. Bumping this is a
18    /// breaking event by design.
19    pub version: u32,
20    pub default_environment: String,
21    #[serde(default)]
22    pub defaults: Defaults,
23    pub environments: BTreeMap<String, EnvironmentConfig>,
24    #[serde(default)]
25    pub resources: ResourcesConfig,
26    #[serde(default)]
27    pub naming: NamingConfig,
28}
29
30#[derive(Debug, Clone, Default, Deserialize)]
31#[serde(deny_unknown_fields)]
32pub struct Defaults {}
33
34#[derive(Debug, Clone, Deserialize)]
35#[serde(deny_unknown_fields)]
36pub struct EnvironmentConfig {
37    pub api_endpoint: Url,
38    /// Name of the environment variable holding the Braze API key. The key
39    /// itself MUST NOT live in this file (§2.3 / §10).
40    pub api_key_env: String,
41}
42
43#[derive(Debug, Clone, Deserialize)]
44#[serde(deny_unknown_fields)]
45pub struct ResourcesConfig {
46    #[serde(default = "default_catalog_schema")]
47    pub catalog_schema: ResourceConfig,
48    #[serde(default = "default_content_block")]
49    pub content_block: ResourceConfig,
50    #[serde(default = "default_email_template")]
51    pub email_template: ResourceConfig,
52    #[serde(default = "default_custom_attribute")]
53    pub custom_attribute: ResourceConfig,
54}
55
56impl ResourcesConfig {
57    pub fn for_kind(&self, kind: crate::resource::ResourceKind) -> &ResourceConfig {
58        use crate::resource::ResourceKind;
59        match kind {
60            ResourceKind::CatalogSchema => &self.catalog_schema,
61            ResourceKind::ContentBlock => &self.content_block,
62            ResourceKind::EmailTemplate => &self.email_template,
63            ResourceKind::CustomAttribute => &self.custom_attribute,
64        }
65    }
66
67    pub fn is_enabled(&self, kind: crate::resource::ResourceKind) -> bool {
68        self.for_kind(kind).enabled
69    }
70}
71
72impl Default for ResourcesConfig {
73    fn default() -> Self {
74        Self {
75            catalog_schema: default_catalog_schema(),
76            content_block: default_content_block(),
77            email_template: default_email_template(),
78            custom_attribute: default_custom_attribute(),
79        }
80    }
81}
82
83#[derive(Debug, Clone, Deserialize)]
84#[serde(deny_unknown_fields)]
85pub struct ResourceConfig {
86    #[serde(default = "default_enabled")]
87    pub enabled: bool,
88    pub path: PathBuf,
89    /// Regex patterns (matched against resource `name`) that mark a
90    /// resource as **managed out of band**. Names matching any pattern
91    /// are skipped by `export`, `diff`, `apply`, and `validate` so
92    /// Braze reserved attributes (`_unset`) or camelCase duplicates
93    /// don't produce noise. See `docs/configuration.md §exclude_patterns`.
94    #[serde(default)]
95    pub exclude_patterns: Vec<String>,
96}
97
98fn default_enabled() -> bool {
99    true
100}
101
102fn default_catalog_schema() -> ResourceConfig {
103    ResourceConfig {
104        enabled: true,
105        path: PathBuf::from("catalogs/"),
106        exclude_patterns: Vec::new(),
107    }
108}
109
110fn default_content_block() -> ResourceConfig {
111    ResourceConfig {
112        enabled: true,
113        path: PathBuf::from("content_blocks/"),
114        exclude_patterns: Vec::new(),
115    }
116}
117
118fn default_email_template() -> ResourceConfig {
119    ResourceConfig {
120        enabled: true,
121        path: PathBuf::from("email_templates/"),
122        exclude_patterns: Vec::new(),
123    }
124}
125
126fn default_custom_attribute() -> ResourceConfig {
127    ResourceConfig {
128        enabled: true,
129        path: PathBuf::from("custom_attributes/registry.yaml"),
130        exclude_patterns: Vec::new(),
131    }
132}
133
134#[derive(Debug, Clone, Default, Deserialize)]
135#[serde(deny_unknown_fields)]
136pub struct NamingConfig {
137    #[serde(default)]
138    pub catalog_name_pattern: Option<String>,
139    #[serde(default)]
140    pub content_block_name_pattern: Option<String>,
141    #[serde(default)]
142    pub custom_attribute_name_pattern: Option<String>,
143}