palladium_cli/commands/
federation.rs1use clap::{Args, Subcommand};
2use schemars::JsonSchema;
3use serde::Serialize;
4use serde_json::{json, Value};
5
6use crate::client::{ControlPlaneClient, Endpoint};
7use crate::CliResult;
8
9#[derive(Subcommand, Debug, Serialize, JsonSchema)]
10#[serde(rename_all = "kebab-case")]
11pub enum FederationCommand {
12 #[command(subcommand)]
14 Policy(FederationPolicyCommand),
15 #[command(subcommand)]
17 Registry(FederationRegistryCommand),
18}
19
20#[derive(Subcommand, Debug, Serialize, JsonSchema)]
21#[serde(rename_all = "kebab-case")]
22pub enum FederationPolicyCommand {
23 Get(FederationPolicyGetArgs),
25 Set(FederationPolicySetArgs),
27}
28
29#[derive(Subcommand, Debug, Serialize, JsonSchema)]
30#[serde(rename_all = "kebab-case")]
31pub enum FederationRegistryCommand {
32 List(FederationRegistryListArgs),
34 Get(FederationRegistryGetArgs),
36}
37
38#[derive(Args, Debug, Serialize, JsonSchema)]
39#[serde(rename_all = "kebab-case")]
40pub struct FederationPolicyGetArgs {
41 #[arg(long)]
43 pub json: bool,
44}
45
46#[derive(Args, Debug, Serialize, JsonSchema)]
47#[serde(rename_all = "kebab-case")]
48pub struct FederationPolicySetArgs {
49 #[arg(long)]
51 pub mode: String,
52 #[arg(long = "prefix")]
54 pub prefixes: Vec<String>,
55 #[arg(long)]
57 pub json: bool,
58}
59
60#[derive(Args, Debug, Serialize, JsonSchema)]
61#[serde(rename_all = "kebab-case")]
62pub struct FederationRegistryListArgs {
63 #[arg(long)]
65 pub prefix: Option<String>,
66 #[arg(long)]
68 pub json: bool,
69}
70
71#[derive(Args, Debug, Serialize, JsonSchema)]
72#[serde(rename_all = "kebab-case")]
73pub struct FederationRegistryGetArgs {
74 pub path: String,
76 #[arg(long)]
78 pub json: bool,
79}
80
81pub fn run(cmd: FederationCommand, endpoint: &Endpoint) -> CliResult {
82 let mut client = ControlPlaneClient::connect_endpoint(endpoint)?;
83 match cmd {
84 FederationCommand::Policy(policy) => match policy {
85 FederationPolicyCommand::Get(args) => {
86 let result = client.call("federation.policy.get", Value::Null)?;
87 if args.json {
88 println!("{}", serde_json::to_string_pretty(&result)?);
89 } else {
90 println!("Federation policy: {}", format_policy_summary(&result));
91 }
92 }
93 FederationPolicyCommand::Set(args) => {
94 let params = json!({
95 "mode": args.mode,
96 "prefixes": args.prefixes,
97 });
98 let result = client.call("federation.policy.set", params)?;
99 if args.json {
100 println!("{}", serde_json::to_string_pretty(&result)?);
101 } else {
102 println!(
103 "Federation policy updated: {}",
104 format_policy_summary(&result)
105 );
106 }
107 }
108 },
109 FederationCommand::Registry(registry) => match registry {
110 FederationRegistryCommand::List(args) => {
111 let mut params = serde_json::Map::new();
112 if let Some(prefix) = &args.prefix {
113 params.insert("prefix".into(), Value::String(prefix.clone()));
114 }
115 let result = client.call("federation.registry.list", Value::Object(params))?;
116 let entries = result.as_array().cloned().unwrap_or_default();
117 if args.json {
118 println!("{}", serde_json::to_string_pretty(&Value::Array(entries))?);
119 } else if entries.is_empty() {
120 println!("No registry entries.");
121 } else {
122 for entry in entries {
123 let path = entry.get("path").and_then(|v| v.as_str()).unwrap_or("?");
124 let engine = entry
125 .get("engine_id")
126 .and_then(|v| v.as_str())
127 .unwrap_or("?");
128 println!("{path} -> {engine}");
129 }
130 }
131 }
132 FederationRegistryCommand::Get(args) => {
133 let result = client.call("federation.registry.get", json!({"path": args.path}))?;
134 if args.json {
135 println!("{}", serde_json::to_string_pretty(&result)?);
136 } else {
137 let path = result.get("path").and_then(|v| v.as_str()).unwrap_or("?");
138 let engine = result
139 .get("engine_id")
140 .and_then(|v| v.as_str())
141 .unwrap_or("?");
142 println!("{path} -> {engine}");
143 }
144 }
145 },
146 }
147 Ok(())
148}
149
150fn format_policy_summary(result: &Value) -> String {
151 let mode = result
152 .get("mode")
153 .and_then(|v| v.as_str())
154 .unwrap_or("unknown");
155 let prefixes = result
156 .get("prefixes")
157 .and_then(|v| v.as_array())
158 .map(|p| {
159 p.iter()
160 .filter_map(|v| v.as_str())
161 .collect::<Vec<_>>()
162 .join(", ")
163 })
164 .unwrap_or_default();
165 if prefixes.is_empty() {
166 mode.to_string()
167 } else {
168 format!("{mode} [{prefixes}]")
169 }
170}