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