1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3
4#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
5pub struct GlobalManifest {
6 pub version: String,
7 #[serde(default)]
8 pub languages: HashMap<String, RuntimeInfo>,
9}
10
11#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
12pub struct RuntimeInfo {
13 pub latest: String,
14 #[serde(skip_serializing_if = "Option::is_none")]
15 pub lts: Option<String>,
16 pub versions: Vec<String>,
17 pub source: String,
18 pub license: String,
19}
20
21#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
22pub struct RuntimeManifest {
23 pub language: String,
24 pub versions: HashMap<String, RuntimeVersion>,
25}
26
27#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
28pub struct RuntimeVersion {
29 pub file: String,
30 pub size: u64,
31 pub sha256: String,
32 pub released: String,
33 #[serde(default)]
34 pub wasi: bool,
35 #[serde(default)]
36 pub features: Vec<String>,
37 pub url: String,
38}
39
40impl GlobalManifest {
41 pub fn new(version: String) -> Self {
42 Self {
43 version,
44 languages: HashMap::new(),
45 }
46 }
47
48 pub fn add_language(&mut self, name: String, info: RuntimeInfo) {
49 self.languages.insert(name, info);
50 }
51
52 pub fn get_language(&self, name: &str) -> Option<&RuntimeInfo> {
53 self.languages.get(name)
54 }
55}
56
57impl RuntimeInfo {
58 pub fn new(latest: String, source: String, license: String) -> Self {
59 Self {
60 latest,
61 lts: None,
62 versions: Vec::new(),
63 source,
64 license,
65 }
66 }
67
68 pub fn with_lts(mut self, lts: String) -> Self {
69 self.lts = Some(lts);
70 self
71 }
72
73 pub fn add_version(&mut self, version: String) {
74 if !self.versions.contains(&version) {
75 self.versions.push(version);
76 }
77 }
78}
79
80impl RuntimeManifest {
81 pub fn new(language: String) -> Self {
82 Self {
83 language,
84 versions: HashMap::new(),
85 }
86 }
87
88 pub fn add_version(&mut self, version: String, info: RuntimeVersion) {
89 self.versions.insert(version, info);
90 }
91
92 pub fn get_version(&self, version: &str) -> Option<&RuntimeVersion> {
93 self.versions.get(version)
94 }
95}
96
97impl RuntimeVersion {
98 pub fn new(file: String, size: u64, sha256: String, released: String, url: String) -> Self {
99 Self {
100 file,
101 size,
102 sha256,
103 released,
104 wasi: false,
105 features: Vec::new(),
106 url,
107 }
108 }
109
110 pub fn with_wasi(mut self, wasi: bool) -> Self {
111 self.wasi = wasi;
112 self
113 }
114
115 pub fn add_feature(&mut self, feature: String) {
116 if !self.features.contains(&feature) {
117 self.features.push(feature);
118 }
119 }
120}
121
122#[cfg(test)]
123mod tests {
124 use super::*;
125
126 #[test]
127 fn test_global_manifest() {
128 let mut manifest = GlobalManifest::new("1.0.0".to_string());
129 assert_eq!(manifest.version, "1.0.0");
130 assert_eq!(manifest.languages.len(), 0);
131
132 let runtime_info = RuntimeInfo::new(
133 "3.11.7".to_string(),
134 "https://github.com/pyodide/pyodide".to_string(),
135 "MIT".to_string(),
136 );
137 manifest.add_language("python".to_string(), runtime_info);
138 assert_eq!(manifest.languages.len(), 1);
139 assert!(manifest.get_language("python").is_some());
140 }
141
142 #[test]
143 fn test_runtime_info() {
144 let mut info = RuntimeInfo::new(
145 "20.2.0".to_string(),
146 "https://nodejs.org".to_string(),
147 "MIT".to_string(),
148 );
149 assert_eq!(info.latest, "20.2.0");
150 assert!(info.lts.is_none());
151
152 info = info.with_lts("18.19.0".to_string());
153 assert_eq!(info.lts, Some("18.19.0".to_string()));
154
155 info.add_version("20.2.0".to_string());
156 info.add_version("18.19.0".to_string());
157 assert_eq!(info.versions.len(), 2);
158
159 info.add_version("20.2.0".to_string());
160 assert_eq!(info.versions.len(), 2);
161 }
162
163 #[test]
164 fn test_runtime_manifest() {
165 let mut manifest = RuntimeManifest::new("python".to_string());
166 assert_eq!(manifest.language, "python");
167 assert_eq!(manifest.versions.len(), 0);
168
169 let version = RuntimeVersion::new(
170 "python-3.11.7.wasm".to_string(),
171 1024,
172 "abc123".to_string(),
173 "2024-01-01".to_string(),
174 "https://example.com/python-3.11.7.wasm".to_string(),
175 );
176 manifest.add_version("3.11.7".to_string(), version);
177 assert_eq!(manifest.versions.len(), 1);
178 assert!(manifest.get_version("3.11.7").is_some());
179 }
180
181 #[test]
182 fn test_runtime_version() {
183 let mut version = RuntimeVersion::new(
184 "python-3.11.7.wasm".to_string(),
185 1024,
186 "abc123".to_string(),
187 "2024-01-01".to_string(),
188 "https://example.com/python-3.11.7.wasm".to_string(),
189 );
190 assert_eq!(version.file, "python-3.11.7.wasm");
191 assert_eq!(version.size, 1024);
192 assert!(!version.wasi);
193
194 version = version.with_wasi(true);
195 assert!(version.wasi);
196
197 version.add_feature("async".to_string());
198 version.add_feature("filesystem".to_string());
199 assert_eq!(version.features.len(), 2);
200
201 version.add_feature("async".to_string());
202 assert_eq!(version.features.len(), 2);
203 }
204
205 #[test]
206 fn test_manifest_serialization() {
207 let manifest = GlobalManifest {
208 version: "1.0.0".to_string(),
209 languages: {
210 let mut map = HashMap::new();
211 map.insert(
212 "python".to_string(),
213 RuntimeInfo {
214 latest: "3.11.7".to_string(),
215 lts: None,
216 versions: vec!["3.11.7".to_string()],
217 source: "https://github.com/pyodide/pyodide".to_string(),
218 license: "MIT".to_string(),
219 },
220 );
221 map
222 },
223 };
224
225 let json = serde_json::to_string(&manifest).unwrap();
226 let parsed: GlobalManifest = serde_json::from_str(&json).unwrap();
227 assert_eq!(manifest, parsed);
228 }
229}