faucet_cli/commands/
schema.rs1use crate::cli::{SchemaArgs, SchemaTarget};
4use crate::error::CliResult;
5use crate::registry::{sink_schema, source_schema};
6use crate::transforms::transform_schema;
7
8pub async fn run(args: SchemaArgs) -> CliResult<()> {
10 let schema = match args.target {
11 SchemaTarget::Config => crate::schema_compose::config_schema(),
12 SchemaTarget::Source { name } => source_schema(&name)?,
13 SchemaTarget::Sink { name } => sink_schema(&name)?,
14 SchemaTarget::Transform { name } => transform_schema(&name)?,
15 SchemaTarget::Dlq => {
16 let dlq_schema = faucet_core::schema_for!(crate::config::DlqSpec);
17 serde_json::to_value(dlq_schema)
18 .unwrap_or_else(|_| serde_json::json!({"type": "object"}))
19 }
20 SchemaTarget::Replication => {
21 let s = faucet_core::schema_for!(crate::replication::spec::ReplicationSpec);
22 serde_json::to_value(s).unwrap_or_else(|_| serde_json::json!({"type": "object"}))
23 }
24 SchemaTarget::Backfill => {
25 let s = faucet_core::schema_for!(crate::backfill::BackfillSpec);
26 serde_json::to_value(s).unwrap_or_else(|_| serde_json::json!({"type": "object"}))
27 }
28 SchemaTarget::Params => {
29 let s = faucet_core::schema_for!(crate::params::ParamSpec);
30 serde_json::to_value(s).unwrap_or_else(|_| serde_json::json!({"type": "object"}))
31 }
32 SchemaTarget::Execution => {
33 let s = faucet_core::schema_for!(crate::config::ExecutionSpec);
34 serde_json::to_value(s).unwrap_or_else(|_| serde_json::json!({"type": "object"}))
35 }
36 SchemaTarget::Resilience => {
37 let s = faucet_core::schema_for!(crate::config::ResilienceSpec);
38 serde_json::to_value(s).unwrap_or_else(|_| serde_json::json!({"type": "object"}))
39 }
40 SchemaTarget::Sla => {
41 let s = faucet_core::schema_for!(crate::sla::SlaSpec);
42 serde_json::to_value(s).unwrap_or_else(|_| serde_json::json!({"type": "object"}))
43 }
44 #[cfg(feature = "quality")]
45 SchemaTarget::Quality => {
46 let quality_schema = faucet_core::schema_for!(faucet_core::QualitySpec);
47 serde_json::to_value(quality_schema)
48 .unwrap_or_else(|_| serde_json::json!({"type": "object"}))
49 }
50 #[cfg(feature = "contract")]
51 SchemaTarget::Contract => {
52 let contract_schema = faucet_core::schema_for!(faucet_core::ContractSpec);
53 serde_json::to_value(contract_schema)
54 .unwrap_or_else(|_| serde_json::json!({"type": "object"}))
55 }
56 #[cfg(feature = "masking")]
57 SchemaTarget::Masking => {
58 let masking_schema = faucet_core::schema_for!(faucet_core::MaskingSpec);
59 serde_json::to_value(masking_schema)
60 .unwrap_or_else(|_| serde_json::json!({"type": "object"}))
61 }
62 #[cfg(feature = "schedule")]
63 SchemaTarget::Schedule => {
64 let s = faucet_core::schema_for!(crate::schedule::spec::ScheduleSpec);
65 serde_json::to_value(s).unwrap_or_else(|_| serde_json::json!({"type": "object"}))
66 }
67 #[cfg(feature = "lineage")]
68 SchemaTarget::Lineage => lineage_schema(),
69 #[cfg(feature = "triggers")]
70 SchemaTarget::Triggers => {
71 let s = faucet_core::schema_for!(crate::serve::triggers::spec::TriggersFile);
72 serde_json::to_value(s).unwrap_or_else(|_| serde_json::json!({"type": "object"}))
73 }
74 SchemaTarget::Test => {
75 let s = faucet_core::schema_for!(crate::pipeline_test::spec::TestSpecFile);
76 serde_json::to_value(s).unwrap_or_else(|_| serde_json::json!({"type": "object"}))
77 }
78 #[cfg(feature = "notify")]
79 SchemaTarget::Notifications => {
80 let s = faucet_core::schema_for!(crate::notify::NotificationSpec);
82 serde_json::to_value(s).unwrap_or_else(|_| serde_json::json!({"type": "object"}))
83 }
84 #[cfg(feature = "catalog")]
85 SchemaTarget::Catalog => {
86 let s = faucet_core::schema_for!(crate::catalog::CatalogSpec);
87 serde_json::to_value(s).unwrap_or_else(|_| serde_json::json!({"type": "object"}))
88 }
89 SchemaTarget::Secrets => serde_json::json!({
90 "title": "Secrets-manager interpolation grammar",
91 "schemes": {
92 "vault": { "syntax": "${vault:<path>[#field]}", "auth": ["VAULT_ADDR", "VAULT_TOKEN", "VAULT_NAMESPACE (optional)"] },
93 "aws-sm": { "syntax": "${aws-sm:<name-or-ARN>[#field]}", "auth": ["aws-config default credential chain"] },
94 "gcp-sm": { "syntax": "${gcp-sm:projects/<p>/secrets/<s>/versions/<v>}", "auth": ["Application Default Credentials"] },
95 "azure-kv": { "syntax": "${azure-kv:<vault>/<secret>[/<version>]}", "auth": ["AZURE_* env / managed identity / az login"] }
96 },
97 "notes": [
98 "#field parses the secret as JSON and extracts one key (vault, aws-sm).",
99 "Resolved at config load; fetched concurrently and de-duplicated; never persisted.",
100 "Build with --features secrets (or per-backend secrets-vault / secrets-aws-sm / ...)."
101 ]
102 }),
103 };
104 let body = serde_json::to_string_pretty(&schema).unwrap_or_else(|_| schema.to_string());
105 println!("{body}");
106 Ok(())
107}
108
109#[cfg(feature = "lineage")]
111pub fn lineage_schema() -> serde_json::Value {
112 serde_json::to_value(faucet_lineage::schemars_schema())
113 .unwrap_or_else(|_| serde_json::json!({"type": "object"}))
114}
115
116#[cfg(test)]
117mod tests {
118 use crate::cli::{SchemaArgs, SchemaTarget};
119
120 #[cfg(feature = "lineage")]
121 #[test]
122 fn schema_lineage_returns_object_schema() {
123 let v = super::lineage_schema();
124 assert_eq!(v["type"], "object");
125 assert!(v["properties"].get("transport").is_some());
126 assert!(v["properties"].get("namespace").is_some());
127 }
128
129 #[tokio::test]
130 async fn schema_replication_target_ok() {
131 let r = super::run(SchemaArgs {
134 target: SchemaTarget::Replication,
135 })
136 .await;
137 assert!(r.is_ok(), "{r:?}");
138 }
139
140 #[tokio::test]
141 async fn schema_execution_target_ok() {
142 let r = super::run(SchemaArgs {
143 target: SchemaTarget::Execution,
144 })
145 .await;
146 assert!(r.is_ok(), "{r:?}");
147 }
148
149 #[test]
150 fn execution_schema_includes_adaptive_batch_size() {
151 let schema = faucet_core::schema_for!(crate::config::ExecutionSpec);
152 let value = serde_json::to_value(schema).expect("execution schema serializes");
153 assert!(value["properties"].get("adaptive_batch_size").is_some());
154 }
155
156 #[tokio::test]
157 async fn schema_sla_target_ok() {
158 let r = super::run(SchemaArgs {
159 target: SchemaTarget::Sla,
160 })
161 .await;
162 assert!(r.is_ok(), "{r:?}");
163 }
164
165 #[test]
166 fn sla_schema_exposes_the_three_checks() {
167 let schema = faucet_core::schema_for!(crate::sla::SlaSpec);
168 let out = serde_json::to_string(&schema).expect("sla schema serializes");
169 assert!(out.contains("max_staleness_secs"), "{out}");
170 assert!(out.contains("min_rows_per_run"), "{out}");
171 assert!(out.contains("volume_anomaly"), "{out}");
172 }
173
174 #[tokio::test]
175 async fn schema_resilience_target_ok() {
176 let r = super::run(SchemaArgs {
177 target: SchemaTarget::Resilience,
178 })
179 .await;
180 assert!(r.is_ok(), "{r:?}");
181 }
182
183 #[test]
184 fn schema_resilience_emits_json_schema() {
185 let schema = faucet_core::schema_for!(crate::config::ResilienceSpec);
189 let out = serde_json::to_string(&schema).expect("resilience schema serializes");
190 assert!(out.contains("max_attempts"), "{out}");
191 assert!(out.contains("circuit_breaker"), "{out}");
192 }
193}