1use std::path::Path;
2
3use futures_util::future::join_all;
4use reqwest;
5use serde::{Deserialize, Serialize};
6use tokio::fs;
7
8#[derive(Debug, Serialize, Deserialize)]
9pub struct Registry {
10 pub name: String,
11 pub description: Option<String>,
12 pub shurikens: Vec<ArmoryItem>,
13}
14
15#[derive(Serialize, Deserialize, Debug, Clone)]
16#[serde(tag = "type")]
17pub enum ArmoryItem {
18 #[serde(rename = "shuriken")]
19 Shuriken {
20 name: String,
21 version: String,
22 description: String,
23 author: String,
24 license: String,
25 platforms: Vec<String>,
26 url: String,
27 },
28
29 #[serde(rename = "bundle")]
30 Bundle {
31 name: String,
32 version: String,
33 description: String,
34 author: String,
35 license: String,
36 shurikens: Vec<String>,
37 },
38}
39
40impl ArmoryItem {
41 pub fn resolve(self) -> Self {
42 match self {
43 ArmoryItem::Shuriken {
44 name,
45 version,
46 description,
47 author,
48 license,
49 platforms,
50 url,
51 } => {
52 let resolved_partial_url = url.replace("{{ os }}", std::env::consts::OS);
53 let resolved_url =
54 resolved_partial_url.replace("{{ arch }}", std::env::consts::ARCH);
55
56 ArmoryItem::Shuriken {
57 name,
58 version,
59 description,
60 author,
61 license,
62 platforms,
63 url: resolved_url,
64 }
65 }
66
67 ArmoryItem::Bundle { .. } => self,
68 }
69 }
70}
71
72pub fn is_absolute_url(url: &str) -> bool {
73 url.starts_with("http://") || url.starts_with("https://")
74}
75
76pub async fn fetch_registry(url: &str) -> Result<Registry, anyhow::Error> {
77 let response = reqwest::get(url).await?;
78 let status = response.status();
79 let text = response.text().await?;
80
81 if !status.is_success() {
82 return Err(anyhow::anyhow!("Failed to fetch registry: HTTP {}", status));
83 }
84
85 let registry: Registry = serde_yaml::from_str(&text)?;
86 Ok(registry)
87}
88
89pub async fn download_shuriken(path: &Path, url: &str) -> Result<(), anyhow::Error> {
90 let response = reqwest::get(url).await?;
91 let bytes = response.bytes().await?.to_vec();
92 fs::write(path, &bytes).await?;
93 Ok(())
94}
95
96pub async fn get_shurikens_from_registries(urls: &[String]) -> Vec<ArmoryItem> {
97 let futures = urls.iter().map(|url| fetch_registry(url));
98 let results = join_all(futures).await;
99
100 let mut all_shurikens = Vec::new();
101
102 for result in results {
103 match result {
104 Ok(registry) => {
105 let shurikens = registry
106 .shurikens
107 .into_iter()
108 .filter(|item| matches!(item, ArmoryItem::Shuriken { .. }));
109
110 all_shurikens.extend(shurikens);
111 }
112 Err(e) => {
113 eprintln!("Failed to fetch registry: {}", e);
114 }
115 }
116 }
117
118 all_shurikens
119}
120
121pub async fn get_shuriken_from_registries(
122 name: String,
123 registries: &[String],
124) -> Option<ArmoryItem> {
125 let shurikens = get_shurikens_from_registries(registries).await;
126
127 shurikens.into_iter().find(|item| match item {
128 ArmoryItem::Shuriken {
129 name: item_name, ..
130 } => item_name == &name,
131 _ => false,
132 })
133}