1use async_trait::async_trait;
2use futures_util::StreamExt;
3use wasm_pkg_common::{
4 Error,
5 package::{PackageRef, Version, VersionReq},
6};
7
8use crate::{
9 ContentStream,
10 release::{Release, VersionInfo},
11};
12
13#[derive(Debug, Default)]
14pub enum VersionSort {
15 #[default]
16 Ascending,
17 Descending,
18}
19
20#[async_trait]
21pub trait PackageLoader: Send {
22 async fn list_all_versions(&self, package: &PackageRef) -> Result<Vec<VersionInfo>, Error>;
23
24 async fn list_matching_versions(
25 &self,
26 package: &PackageRef,
27 predicate: VersionReq,
28 sort: VersionSort,
29 ) -> Result<Vec<VersionInfo>, Error> {
30 let versions = match self.list_all_versions(package).await {
31 Ok(v) => v,
32 Err(Error::PackageNotFound) => Vec::new(),
33 Err(e) => return Err(e),
34 };
35
36 let mut matching: Vec<VersionInfo> = versions
37 .into_iter()
38 .filter(|v| predicate.matches(&v.version))
39 .collect();
40
41 matching.sort();
42 if matches!(sort, VersionSort::Descending) {
43 matching.reverse();
44 }
45
46 Ok(matching)
47 }
48
49 async fn get_release(&self, package: &PackageRef, version: &Version) -> Result<Release, Error>;
50
51 async fn stream_content_unvalidated(
52 &self,
53 package: &PackageRef,
54 release: &Release,
55 ) -> Result<ContentStream, Error>;
56
57 async fn stream_content(
58 &self,
59 package: &PackageRef,
60 release: &Release,
61 ) -> Result<ContentStream, Error> {
62 let stream = self.stream_content_unvalidated(package, release).await?;
63 Ok(release.content_digest.validating_stream(stream).boxed())
64 }
65}
66
67#[cfg(test)]
68mod tests {
69 use super::{ContentStream, PackageLoader, Release, VersionInfo, VersionSort};
70 use async_trait::async_trait;
71 use rstest::rstest;
72 use wasm_pkg_common::{
73 Error,
74 package::{PackageRef, Version, VersionReq},
75 };
76
77 #[derive(Clone, Debug)]
78 struct VerifiablePackageLoader {
79 history: Vec<VersionInfo>,
80 }
81
82 impl VerifiablePackageLoader {
83 fn new(history: &[Version]) -> Self {
84 Self {
85 history: history
86 .iter()
87 .cloned()
88 .map(|version| VersionInfo {
89 version,
90 yanked: false,
91 })
92 .collect(),
93 }
94 }
95 }
96
97 #[async_trait]
98 impl PackageLoader for VerifiablePackageLoader {
99 async fn list_all_versions(
100 &self,
101 _package: &PackageRef,
102 ) -> Result<Vec<VersionInfo>, Error> {
103 Ok(self.history.clone())
104 }
105
106 async fn get_release(
107 &self,
108 _package: &PackageRef,
109 _version: &Version,
110 ) -> Result<Release, Error> {
111 panic!("get_release is not needed in this unit test")
112 }
113
114 async fn stream_content_unvalidated(
115 &self,
116 _package: &PackageRef,
117 _release: &Release,
118 ) -> Result<ContentStream, Error> {
119 panic!("stream_content_unvalidated is not needed in this unit test")
120 }
121 }
122
123 fn v(input: &str) -> Version {
124 input.parse().expect("valid semver in test case")
125 }
126
127 fn versions(inputs: &[&str]) -> Vec<Version> {
128 inputs.iter().map(|s| v(s)).collect()
129 }
130
131 #[rstest]
134 #[case::target_0_0_0(
135 "~0.0.*",
136 VersionSort::Ascending,
137 &["0.0.0", "0.0.1", "0.1.0", "1.0.0"],
138 &["0.0.0", "0.0.1"]
139 )]
140 #[case::target_0_0_3(
141 "~0.0.*",
142 VersionSort::Ascending,
143 &["0.0.0", "0.0.3", "0.0.7", "0.1.0"],
144 &["0.0.0", "0.0.3", "0.0.7"]
145 )]
146 #[case::target_1_0_0(
147 "~1.0.*",
148 VersionSort::Ascending,
149 &["1.0.0", "1.0.9", "1.1.0", "2.0.0"],
150 &["1.0.0", "1.0.9"]
151 )]
152 #[case::target_2_2_0(
153 "~2.2.*",
154 VersionSort::Ascending,
155 &["2.1.9", "2.2.0", "2.2.5", "2.3.0"],
156 &["2.2.0", "2.2.5"]
157 )]
158 #[case::empty_history("~1.2.*", VersionSort::Ascending, &[], &[])]
159 #[case::no_matching_major_minor_in_history(
160 "~3.4.*",
161 VersionSort::Ascending,
162 &["3.5.0", "3.6.1", "4.4.5"],
163 &[]
164 )]
165 #[case::all_patches_in_series(
166 "~1.2.*",
167 VersionSort::Ascending,
168 &["1.2.0", "1.2.1", "1.2.99", "1.3.0", "0.2.9"],
169 &["1.2.0", "1.2.1", "1.2.99"]
170 )]
171 #[case::pre_release_excluded_from_series(
176 "~1.2.*",
177 VersionSort::Ascending,
178 &["1.2.0", "1.2.1-beta.2", "1.2.4+build.7", "1.3.0-alpha.1"],
179 &["1.2.0", "1.2.4+build.7"]
180 )]
181 #[case::duplication_is_preserved(
182 "~1.2.*",
183 VersionSort::Ascending,
184 &["1.2.1", "1.2.1", "1.2.2", "1.3.0"],
185 &["1.2.1", "1.2.1", "1.2.2"]
186 )]
187 #[case::descending_sort_orders_matches_high_to_low(
188 "~1.2.*",
189 VersionSort::Descending,
190 &["1.2.0", "1.2.5", "1.2.1", "1.3.0"],
191 &["1.2.5", "1.2.1", "1.2.0"]
192 )]
193 #[case::descending_sort_with_empty_matches(
194 "~9.9.*",
195 VersionSort::Descending,
196 &["1.0.0", "2.0.0"],
197 &[]
198 )]
199 #[tokio::test]
200 async fn list_matching_versions_filters_by_version_req(
201 #[case] req: &str,
202 #[case] sort: VersionSort,
203 #[case] history: &[&str],
204 #[case] expected: &[&str],
205 ) {
206 let history = versions(history);
207 let expected = versions(expected);
208 let filter = VersionReq::parse(req).expect("valid series req");
209 let package: PackageRef = "example:package".parse().expect("valid package ref");
210
211 let loader = VerifiablePackageLoader::new(&history);
212
213 let got: Vec<Version> = loader
214 .list_matching_versions(&package, filter, sort)
215 .await
216 .expect("list_matching_versions should succeed")
217 .into_iter()
218 .map(|v| v.version)
219 .collect();
220
221 assert_eq!(got, expected.as_slice());
222 }
223}