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