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