1use std::collections::{BTreeMap, HashMap};
2
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5
6use crate::{
7 error::ThermiteError,
8 model::{Mod, ModVersion},
9};
10
11#[derive(Deserialize, Serialize, Clone, Debug)]
12struct PackageListing {
13 name: String,
14 owner: String,
15 versions: Vec<PackageVersion>,
16 #[serde(flatten)]
17 _extra: HashMap<String, Value>,
18}
19
20#[derive(Deserialize, Serialize, Clone, Debug)]
21struct PackageVersion {
22 dependencies: Vec<String>,
23 description: String,
24 download_url: String,
25 file_size: u64,
26 version_number: String,
27 full_name: String,
28
29 #[serde(flatten)]
30 _extra: HashMap<String, Value>,
31}
32
33pub fn get_package_index() -> Result<Vec<Mod>, ThermiteError> {
39 let raw = ureq::get("https://northstar.thunderstore.io/c/northstar/api/v1/package/")
40 .header("accept", "application/json")
41 .call()?;
42 let parsed: Vec<PackageListing> = serde_json::from_reader(raw.into_body().into_reader())?;
43 let index = map_response(&parsed);
44
45 Ok(index)
46}
47
48fn map_response(res: &[PackageListing]) -> Vec<Mod> {
49 res.iter()
50 .map(|e| {
51 let versions = &e.versions;
52 let latest = versions[0].clone();
53 let mut urls = BTreeMap::new();
54
55 for v in versions {
56 urls.insert(
57 v.version_number.clone(),
58 ModVersion {
59 name: e.name.clone(),
60 full_name: v.full_name.clone(),
61 version: v.version_number.clone(),
62 desc: v.description.clone(),
63 file_size: v.file_size,
64 deps: v
65 .dependencies
66 .iter()
67 .filter(|e| !e.contains("northstar-Northstar"))
68 .cloned()
69 .collect::<Vec<String>>(),
70 installed: false,
71 global: false,
72 url: v.download_url.clone(),
73 },
74 );
75 }
76
77 Mod {
78 name: e.name.clone(),
79 author: e.owner.clone(),
80 latest: latest.version_number,
81 versions: urls,
82 installed: false,
83 global: false,
84 upgradable: false,
85 }
86 })
87 .collect()
88}
89
90#[cfg(test)]
91mod test {
92 use std::collections::{BTreeMap, HashMap};
93
94 use crate::model::{Mod, ModVersion};
95
96 use super::{get_package_index, map_response, PackageListing, PackageVersion};
97
98 #[test]
99 fn get_packages_from_tstore() {
100 let index = get_package_index();
101 assert!(index.is_ok());
102 let index = index.unwrap();
103 assert!(!index.is_empty());
104 let mut deps = 0;
105 for f in index {
106 for d in f.versions.get(&f.latest).unwrap().deps.iter() {
107 assert_ne!(d, "northstar-Northstar");
108 deps += 1;
109 }
110 }
111
112 assert_ne!(0, deps);
113 }
114
115 #[test]
116 fn map_thunderstore_response() {
117 let test_data = [PackageListing {
118 name: "Foo".into(),
119 owner: "Bar".into(),
120 versions: vec![PackageVersion {
121 dependencies: vec!["something".into()],
122 description: "Test".into(),
123 download_url: "localhost".into(),
124 file_size: 420,
125 version_number: "0.1.0".into(),
126 full_name: "Bar-Foo-0.1.0".into(),
127 _extra: HashMap::new(),
128 }],
129 _extra: HashMap::new(),
130 }];
131
132 let expected = [Mod {
133 name: "Foo".into(),
134 author: "Bar".into(),
135 latest: "0.1.0".into(),
136 installed: false,
137 upgradable: false,
138 global: false,
139 versions: BTreeMap::from([(
140 "0.1.0".into(),
141 ModVersion {
142 name: "Foo".into(),
143 full_name: "Bar-Foo-0.1.0".into(),
144 version: "0.1.0".into(),
145 url: "localhost".into(),
146 desc: "Test".into(),
147 deps: vec!["something".into()],
148 installed: false,
149 global: false,
150 file_size: 420,
151 },
152 )]),
153 }];
154
155 let res = map_response(&test_data);
156 assert!(!res.is_empty());
157 assert_eq!(&res[0], &expected[0]);
158 }
159}