Skip to main content

ninja/common/
registry.rs

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}