Skip to main content

torsh_package/
manifest.rs

1//! Package manifest handling
2
3use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6
7/// Package manifest containing metadata and contents information
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct PackageManifest {
10    /// Package name
11    pub name: String,
12
13    /// Package version
14    pub version: String,
15
16    /// Package format version
17    pub format_version: String,
18
19    /// Creation timestamp
20    pub created_at: DateTime<Utc>,
21
22    /// Package author
23    pub author: Option<String>,
24
25    /// Package description
26    pub description: Option<String>,
27
28    /// License
29    pub license: Option<String>,
30
31    /// List of modules in the package
32    pub modules: Vec<ModuleInfo>,
33
34    /// List of resources in the package
35    pub resources: Vec<ResourceInfo>,
36
37    /// Dependencies
38    pub dependencies: HashMap<String, String>,
39
40    /// Additional metadata
41    pub metadata: HashMap<String, String>,
42
43    /// Cryptographic signature (if signed)
44    pub signature: Option<PackageSignature>,
45}
46
47/// Information about a module in the package
48#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct ModuleInfo {
50    /// Module name
51    pub name: String,
52
53    /// Module class name
54    pub class_name: String,
55
56    /// Module version
57    pub version: String,
58
59    /// Module dependencies
60    pub dependencies: Vec<String>,
61
62    /// Whether source code is included
63    pub has_source: bool,
64}
65
66/// Information about a resource in the package
67#[derive(Debug, Clone, Serialize, Deserialize)]
68pub struct ResourceInfo {
69    /// Resource name
70    pub name: String,
71
72    /// Resource type
73    pub resource_type: String,
74
75    /// Resource size in bytes
76    pub size: u64,
77
78    /// SHA256 hash
79    pub sha256: String,
80
81    /// Compression method
82    pub compression: Option<String>,
83}
84
85/// Package signature for verification
86#[derive(Debug, Clone, Serialize, Deserialize)]
87pub struct PackageSignature {
88    /// Signature algorithm
89    pub algorithm: String,
90
91    /// Public key ID
92    pub key_id: String,
93
94    /// Signature data
95    pub signature: String,
96
97    /// Timestamp
98    pub signed_at: DateTime<Utc>,
99}
100
101impl PackageManifest {
102    /// Create a new package manifest
103    pub fn new(name: String, version: String) -> Self {
104        Self {
105            name,
106            version,
107            format_version: crate::PACKAGE_FORMAT_VERSION.to_string(),
108            created_at: Utc::now(),
109            author: None,
110            description: None,
111            license: None,
112            modules: Vec::new(),
113            resources: Vec::new(),
114            dependencies: HashMap::new(),
115            metadata: HashMap::new(),
116            signature: None,
117        }
118    }
119
120    /// Validate manifest
121    pub fn validate(&self) -> Result<(), String> {
122        // Check required fields
123        if self.name.is_empty() {
124            return Err("Package name cannot be empty".to_string());
125        }
126
127        if self.version.is_empty() {
128            return Err("Package version cannot be empty".to_string());
129        }
130
131        // Validate version format
132        if semver::Version::parse(&self.version).is_err() {
133            return Err(format!("Invalid version format: {}", self.version));
134        }
135
136        // Check format version compatibility
137        let current_version = semver::Version::parse(crate::PACKAGE_FORMAT_VERSION)
138            .expect("PACKAGE_FORMAT_VERSION constant should be valid semver");
139        let manifest_version = semver::Version::parse(&self.format_version)
140            .map_err(|_| "Invalid format version in manifest")?;
141
142        if manifest_version.major != current_version.major {
143            return Err(format!(
144                "Incompatible package format version: {} (expected {}.x.x)",
145                self.format_version, current_version.major
146            ));
147        }
148
149        // Validate modules
150        for module in &self.modules {
151            if module.name.is_empty() {
152                return Err("Module name cannot be empty".to_string());
153            }
154        }
155
156        Ok(())
157    }
158
159    /// Add author information
160    pub fn with_author(mut self, author: String) -> Self {
161        self.author = Some(author);
162        self
163    }
164
165    /// Add description
166    pub fn with_description(mut self, description: String) -> Self {
167        self.description = Some(description);
168        self
169    }
170
171    /// Add license
172    pub fn with_license(mut self, license: String) -> Self {
173        self.license = Some(license);
174        self
175    }
176
177    /// Get total package size
178    pub fn total_size(&self) -> u64 {
179        self.resources.iter().map(|r| r.size).sum()
180    }
181
182    /// Get module by name
183    pub fn get_module(&self, name: &str) -> Option<&ModuleInfo> {
184        self.modules.iter().find(|m| m.name == name)
185    }
186
187    /// Get resource by name
188    pub fn get_resource(&self, name: &str) -> Option<&ResourceInfo> {
189        self.resources.iter().find(|r| r.name == name)
190    }
191}
192
193/// Manifest builder for convenient creation
194pub struct ManifestBuilder {
195    manifest: PackageManifest,
196}
197
198impl ManifestBuilder {
199    /// Create a new manifest builder
200    pub fn new(name: String, version: String) -> Self {
201        Self {
202            manifest: PackageManifest::new(name, version),
203        }
204    }
205
206    /// Set author
207    pub fn author(mut self, author: String) -> Self {
208        self.manifest.author = Some(author);
209        self
210    }
211
212    /// Set description
213    pub fn description(mut self, description: String) -> Self {
214        self.manifest.description = Some(description);
215        self
216    }
217
218    /// Set license
219    pub fn license(mut self, license: String) -> Self {
220        self.manifest.license = Some(license);
221        self
222    }
223
224    /// Add a module
225    pub fn add_module(mut self, module: ModuleInfo) -> Self {
226        self.manifest.modules.push(module);
227        self
228    }
229
230    /// Add a resource
231    pub fn add_resource(mut self, resource: ResourceInfo) -> Self {
232        self.manifest.resources.push(resource);
233        self
234    }
235
236    /// Add a dependency
237    pub fn add_dependency(mut self, name: String, version: String) -> Self {
238        self.manifest.dependencies.insert(name, version);
239        self
240    }
241
242    /// Add metadata
243    pub fn add_metadata(mut self, key: String, value: String) -> Self {
244        self.manifest.metadata.insert(key, value);
245        self
246    }
247
248    /// Build the manifest
249    pub fn build(self) -> PackageManifest {
250        self.manifest
251    }
252}
253
254#[cfg(test)]
255mod tests {
256    use super::*;
257
258    #[test]
259    fn test_manifest_creation() {
260        let manifest = PackageManifest::new("test_package".to_string(), "1.0.0".to_string());
261
262        assert_eq!(manifest.name, "test_package");
263        assert_eq!(manifest.version, "1.0.0");
264        assert_eq!(manifest.format_version, crate::PACKAGE_FORMAT_VERSION);
265    }
266
267    #[test]
268    fn test_manifest_validation() {
269        let mut manifest = PackageManifest::new("test".to_string(), "1.0.0".to_string());
270        assert!(manifest.validate().is_ok());
271
272        // Invalid version
273        manifest.version = "invalid".to_string();
274        assert!(manifest.validate().is_err());
275
276        // Empty name
277        manifest.version = "1.0.0".to_string();
278        manifest.name = String::new();
279        assert!(manifest.validate().is_err());
280    }
281
282    #[test]
283    fn test_manifest_builder() {
284        let manifest = ManifestBuilder::new("test".to_string(), "1.0.0".to_string())
285            .author("Test Author".to_string())
286            .description("Test package".to_string())
287            .license("MIT".to_string())
288            .add_dependency("torsh-core".to_string(), "0.1.0-alpha.2".to_string())
289            .build();
290
291        assert_eq!(manifest.author.as_deref(), Some("Test Author"));
292        assert_eq!(manifest.description.as_deref(), Some("Test package"));
293        assert_eq!(manifest.license.as_deref(), Some("MIT"));
294        assert_eq!(
295            manifest.dependencies.get("torsh-core"),
296            Some(&"0.1.0-alpha.2".to_string())
297        );
298    }
299}