1use crate::error::{Error, Result};
2use crate::output::{print_success, OutputFormat};
3use clap::Subcommand;
4use serde::Serialize;
5use std::sync::Arc;
6use tap_agent::did::{
7 DIDGenerationOptions, DIDKeyGenerator, KeyType, MultiResolver, SyncDIDResolver,
8};
9use tap_agent::storage::KeyStorage;
10
11#[derive(Subcommand, Debug)]
12pub enum DidCommands {
13 Generate {
15 #[arg(short, long, default_value = "key")]
17 method: String,
18 #[arg(short = 't', long, default_value = "ed25519")]
20 key_type: String,
21 #[arg(short, long)]
23 domain: Option<String>,
24 #[arg(short, long)]
26 save: bool,
27 #[arg(long)]
29 default: bool,
30 #[arg(short, long)]
32 label: Option<String>,
33 },
34 Lookup {
36 did: String,
38 },
39 Keys {
41 #[command(subcommand)]
42 cmd: Option<KeysCommands>,
43 },
44}
45
46#[derive(Subcommand, Debug)]
47pub enum KeysCommands {
48 List,
50 View {
52 did_or_label: String,
54 },
55 SetDefault {
57 did_or_label: String,
59 },
60 Delete {
62 did_or_label: String,
64 },
65 Relabel {
67 did_or_label: String,
69 new_label: String,
71 },
72}
73
74#[derive(Debug, Serialize)]
75struct GeneratedDidResponse {
76 did: String,
77 key_type: String,
78 public_key: String,
79 saved: bool,
80 is_default: bool,
81}
82
83#[derive(Debug, Serialize)]
84struct KeyInfo {
85 did: String,
86 label: String,
87 key_type: String,
88 public_key: String,
89 is_default: bool,
90}
91
92#[derive(Debug, Serialize)]
93struct KeyListResponse {
94 keys: Vec<KeyInfo>,
95 total: usize,
96}
97
98pub async fn handle(cmd: &DidCommands, format: OutputFormat) -> Result<()> {
99 match cmd {
100 DidCommands::Generate {
101 method,
102 key_type,
103 domain,
104 save,
105 default,
106 label,
107 } => {
108 handle_generate(
109 method,
110 key_type,
111 domain.as_deref(),
112 *save,
113 *default,
114 label.as_deref(),
115 format,
116 )
117 .await
118 }
119 DidCommands::Lookup { did } => handle_lookup(did, format).await,
120 DidCommands::Keys { cmd } => handle_keys(cmd.as_ref(), format).await,
121 }
122}
123
124async fn handle_generate(
125 method: &str,
126 key_type_str: &str,
127 domain: Option<&str>,
128 save: bool,
129 set_default: bool,
130 label: Option<&str>,
131 format: OutputFormat,
132) -> Result<()> {
133 let key_type = match key_type_str.to_lowercase().as_str() {
134 "ed25519" => KeyType::Ed25519,
135 _ => KeyType::Ed25519,
136 };
137
138 let did_options = DIDGenerationOptions { key_type };
139 let generator = DIDKeyGenerator::new();
140
141 let generated_key = match method.to_lowercase().as_str() {
142 "key" => generator.generate_did(did_options)?,
143 "web" => {
144 let domain =
145 domain.ok_or_else(|| Error::invalid_parameter("Domain is required for did:web"))?;
146 generator.generate_web_did(domain, did_options)?
147 }
148 _ => generator.generate_did(did_options)?,
149 };
150
151 if save {
152 let stored_key = if let Some(label) = label {
153 KeyStorage::from_generated_key_with_label(&generated_key, label)
154 } else {
155 KeyStorage::from_generated_key(&generated_key)
156 };
157
158 let mut storage = KeyStorage::load_default().unwrap_or_else(|_| KeyStorage::new());
159 storage.add_key(stored_key);
160
161 if set_default {
162 storage.default_did = Some(generated_key.did.clone());
163 }
164
165 storage
166 .save_default()
167 .map_err(|e| Error::command_failed(format!("Failed to save key: {}", e)))?;
168 }
169
170 let response = GeneratedDidResponse {
171 did: generated_key.did,
172 key_type: format!("{:?}", generated_key.key_type),
173 public_key: generated_key
174 .public_key
175 .iter()
176 .map(|b| format!("{:02x}", b))
177 .collect::<String>(),
178 saved: save,
179 is_default: set_default,
180 };
181 print_success(format, &response);
182 Ok(())
183}
184
185async fn handle_lookup(did: &str, format: OutputFormat) -> Result<()> {
186 let resolver = Arc::new(MultiResolver::default());
187 let did_doc = resolver.resolve(did).await?;
188
189 match did_doc {
190 Some(doc) => {
191 print_success(format, &doc);
192 Ok(())
193 }
194 None => Err(Error::command_failed(format!("DID not found: {}", did))),
195 }
196}
197
198async fn handle_keys(cmd: Option<&KeysCommands>, format: OutputFormat) -> Result<()> {
199 let mut storage = KeyStorage::load_default().unwrap_or_else(|_| KeyStorage::new());
200 let default_did = storage.default_did.clone();
201
202 match cmd {
203 Some(KeysCommands::List) | None => {
204 let keys: Vec<KeyInfo> = storage
205 .keys
206 .iter()
207 .map(|(did, key)| KeyInfo {
208 did: did.clone(),
209 label: key.label.clone(),
210 key_type: format!("{:?}", key.key_type),
211 public_key: key.public_key.clone(),
212 is_default: default_did.as_deref() == Some(did.as_str()),
213 })
214 .collect();
215
216 let response = KeyListResponse {
217 total: keys.len(),
218 keys,
219 };
220 print_success(format, &response);
221 Ok(())
222 }
223 Some(KeysCommands::View { did_or_label }) => {
224 let key = storage
225 .find_by_label(did_or_label)
226 .or_else(|| storage.keys.get(did_or_label))
227 .ok_or_else(|| {
228 Error::command_failed(format!("Key '{}' not found", did_or_label))
229 })?;
230
231 let info = KeyInfo {
232 did: key.did.clone(),
233 label: key.label.clone(),
234 key_type: format!("{:?}", key.key_type),
235 public_key: key.public_key.clone(),
236 is_default: default_did.as_deref() == Some(key.did.as_str()),
237 };
238 print_success(format, &info);
239 Ok(())
240 }
241 Some(KeysCommands::SetDefault { did_or_label }) => {
242 let did = if let Some(key) = storage.find_by_label(did_or_label) {
243 key.did.clone()
244 } else if storage.keys.contains_key(did_or_label) {
245 did_or_label.to_string()
246 } else {
247 return Err(Error::command_failed(format!(
248 "Key '{}' not found",
249 did_or_label
250 )));
251 };
252
253 storage.default_did = Some(did.clone());
254 storage
255 .save_default()
256 .map_err(|e| Error::command_failed(format!("Failed to save: {}", e)))?;
257
258 #[derive(Serialize)]
259 struct SetDefaultResponse {
260 did: String,
261 status: String,
262 }
263 print_success(
264 format,
265 &SetDefaultResponse {
266 did,
267 status: "default_set".to_string(),
268 },
269 );
270 Ok(())
271 }
272 Some(KeysCommands::Delete { did_or_label }) => {
273 let did = if let Some(key) = storage.find_by_label(did_or_label) {
274 key.did.clone()
275 } else if storage.keys.contains_key(did_or_label) {
276 did_or_label.to_string()
277 } else {
278 return Err(Error::command_failed(format!(
279 "Key '{}' not found",
280 did_or_label
281 )));
282 };
283
284 storage.keys.remove(&did);
285 if storage.default_did.as_deref() == Some(&did) {
286 storage.default_did = storage.keys.keys().next().cloned();
287 }
288
289 storage
290 .save_default()
291 .map_err(|e| Error::command_failed(format!("Failed to save: {}", e)))?;
292
293 #[derive(Serialize)]
294 struct DeleteResponse {
295 did: String,
296 status: String,
297 }
298 print_success(
299 format,
300 &DeleteResponse {
301 did,
302 status: "deleted".to_string(),
303 },
304 );
305 Ok(())
306 }
307 Some(KeysCommands::Relabel {
308 did_or_label,
309 new_label,
310 }) => {
311 let did = if let Some(key) = storage.find_by_label(did_or_label) {
312 key.did.clone()
313 } else if storage.keys.contains_key(did_or_label) {
314 did_or_label.to_string()
315 } else {
316 return Err(Error::command_failed(format!(
317 "Key '{}' not found",
318 did_or_label
319 )));
320 };
321
322 storage
323 .update_label(&did, new_label)
324 .map_err(|e| Error::command_failed(format!("Failed to relabel: {}", e)))?;
325 storage
326 .save_default()
327 .map_err(|e| Error::command_failed(format!("Failed to save: {}", e)))?;
328
329 #[derive(Serialize)]
330 struct RelabelResponse {
331 did: String,
332 new_label: String,
333 status: String,
334 }
335 print_success(
336 format,
337 &RelabelResponse {
338 did,
339 new_label: new_label.clone(),
340 status: "relabeled".to_string(),
341 },
342 );
343 Ok(())
344 }
345 }
346}