loadsmith_registry/registries/
offline.rs1use std::{collections::HashMap, pin::Pin};
2
3use loadsmith_core::{Checksum, Dependency, FileUrl, PackageId, PackageRef, Version};
4use serde::{Deserialize, Serialize};
5
6use crate::{Error, Registry, ResolvedVersion, Result, VersionInfo};
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct Package {
10 pub id: PackageId,
11 pub versions: Vec<PackageVersion>,
12}
13
14impl Package {
15 pub fn new(id: impl Into<PackageId>, versions: Vec<PackageVersion>) -> Self {
16 Self {
17 id: id.into(),
18 versions,
19 }
20 }
21
22 fn version_by_version<'a>(&'a self, version: &Version) -> Result<&'a PackageVersion> {
23 self.versions
24 .iter()
25 .find(|v| v.version == *version)
26 .ok_or(Error::VersionNotFound)
27 }
28}
29
30#[derive(Debug, Clone, Serialize, Deserialize)]
31pub struct PackageVersion {
32 pub version: Version,
33 pub url: FileUrl,
34 #[serde(default)]
35 pub size: Option<u64>,
36 #[serde(default)]
37 pub checksum: Option<Checksum>,
38 pub deps: Vec<Dependency>,
39}
40
41impl PackageVersion {
42 pub fn new(version: impl Into<Version>, download_url: impl Into<FileUrl>) -> Self {
43 Self {
44 version: version.into(),
45 url: download_url.into(),
46 size: None,
47 checksum: None,
48 deps: Vec::new(),
49 }
50 }
51
52 pub fn with_size(mut self, size: u64) -> Self {
53 self.size = Some(size);
54 self
55 }
56
57 pub fn with_checksum(mut self, checksum: impl Into<Checksum>) -> Self {
58 self.checksum = Some(checksum.into());
59 self
60 }
61
62 pub fn with_deps(mut self, deps: Vec<Dependency>) -> Self {
63 self.deps = deps;
64 self
65 }
66}
67
68#[derive(Debug, Clone, Serialize, Deserialize, Default)]
69pub struct OfflineRegistry {
70 packages: HashMap<PackageId, Package>,
71}
72
73impl OfflineRegistry {
74 pub fn new(packages: HashMap<PackageId, Package>) -> Self {
75 Self { packages }
76 }
77
78 fn package_by_id<'a>(&'a self, id: &PackageId) -> Result<&'a Package> {
79 self.packages.get(id).ok_or(Error::PackageNotFound)
80 }
81}
82
83impl Registry for OfflineRegistry {
84 fn version_info<'a>(
85 &'a self,
86 id: &'a PackageId,
87 _metadata: Option<&'a serde_json::Value>,
88 ) -> Pin<Box<dyn Future<Output = Result<Vec<VersionInfo>>> + 'a>> {
89 Box::pin(async move {
90 let package = self.package_by_id(id)?;
91
92 let versions = package
93 .versions
94 .iter()
95 .map(|v| VersionInfo {
96 version: v.version.clone(),
97 })
98 .collect();
99
100 Ok(versions)
101 })
102 }
103
104 fn resolve<'a>(
105 &'a self,
106 ref_: &'a PackageRef,
107 _metadata: Option<&'a serde_json::Value>,
108 ) -> Pin<Box<dyn Future<Output = Result<ResolvedVersion>> + 'a>> {
109 Box::pin(async move {
110 let version = self
111 .package_by_id(ref_.id())?
112 .version_by_version(ref_.version())?;
113
114 Ok(ResolvedVersion {
115 url: version.url.clone(),
116 size: version.size,
117 checksum: version.checksum.clone(),
118 deps: version.deps.clone(),
119 })
120 })
121 }
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127
128 use std::assert_matches;
129
130 #[tokio::test]
131 async fn it_works() {
132 let registry = OfflineRegistry::new(HashMap::from_iter([(
133 PackageId::new("author-name"),
134 Package::new(
135 PackageId::new("author-name"),
136 vec![
137 PackageVersion::new(
138 Version::new(1, 0, 0),
139 FileUrl::try_from_url("https://example.com/package-1.0.0.zip").unwrap(),
140 ),
141 PackageVersion::new(
142 Version::new(1, 1, 0),
143 FileUrl::try_from_url("https://example.com/package-1.1.0.zip").unwrap(),
144 ),
145 ],
146 ),
147 )]));
148
149 let versions = registry
150 .version_info(&PackageId::new("author-name"), None)
151 .await
152 .unwrap();
153
154 assert_eq!(versions.len(), 2);
155
156 let non_existent_package = registry
157 .version_info(&PackageId::new("non-existent"), None)
158 .await;
159
160 assert_matches!(non_existent_package, Err(Error::PackageNotFound));
161 }
162}