wasmer_cli/commands/package/
search.rs1use wasmer_backend_api::types::{DateTime, SearchPackageVersion};
4use wasmer_sdk::package::search::{
5 CountComparison, CountFilter, PackageOrderBy, PackagesFilter, SearchOptions, SearchOrderSort,
6 SearchPublishDate,
7};
8
9use crate::{
10 commands::AsyncCliCommand, config::WasmerEnv, opts::ListFormatOpts, utils::render::ListFormat,
11};
12
13const NO_PACKAGES_FOUND_MESSAGE: &str = "No packages found.";
14
15#[derive(clap::ValueEnum, Clone, Copy, Debug)]
17enum OrderBy {
18 Alphabetically,
19 Size,
20 Downloads,
21 Published,
22 Created,
23 Likes,
24}
25
26impl From<OrderBy> for PackageOrderBy {
27 fn from(value: OrderBy) -> Self {
28 match value {
29 OrderBy::Alphabetically => PackageOrderBy::Alphabetically,
30 OrderBy::Size => PackageOrderBy::Size,
31 OrderBy::Downloads => PackageOrderBy::TotalDownloads,
32 OrderBy::Published => PackageOrderBy::PublishedDate,
33 OrderBy::Created => PackageOrderBy::CreatedDate,
34 OrderBy::Likes => PackageOrderBy::TotalLikes,
35 }
36 }
37}
38
39#[derive(clap::ValueEnum, Clone, Copy, Debug)]
41enum Sort {
42 Asc,
43 Desc,
44}
45
46impl From<Sort> for SearchOrderSort {
47 fn from(value: Sort) -> Self {
48 match value {
49 Sort::Asc => SearchOrderSort::Asc,
50 Sort::Desc => SearchOrderSort::Desc,
51 }
52 }
53}
54
55#[derive(clap::ValueEnum, Clone, Copy, Debug)]
57#[allow(clippy::enum_variant_names)] enum PublishedWithin {
59 LastDay,
60 LastWeek,
61 LastMonth,
62 LastYear,
63}
64
65impl From<PublishedWithin> for SearchPublishDate {
66 fn from(value: PublishedWithin) -> Self {
67 match value {
68 PublishedWithin::LastDay => SearchPublishDate::LastDay,
69 PublishedWithin::LastWeek => SearchPublishDate::LastWeek,
70 PublishedWithin::LastMonth => SearchPublishDate::LastMonth,
71 PublishedWithin::LastYear => SearchPublishDate::LastYear,
72 }
73 }
74}
75
76fn at_least(count: i32) -> CountFilter {
78 CountFilter {
79 count: Some(count),
80 comparison: Some(CountComparison::GreaterThanOrEqual),
81 }
82}
83
84fn parse_rfc3339(value: &str) -> Result<DateTime, String> {
87 time::OffsetDateTime::parse(value, &time::format_description::well_known::Rfc3339)
88 .map(|_| DateTime(value.to_string()))
89 .map_err(|e| {
90 format!("`{value}` is not an RFC3339 timestamp (e.g. 2024-01-01T00:00:00Z): {e}")
91 })
92}
93
94fn render_search_results(format: ListFormat, results: &[SearchPackageVersion]) -> String {
95 if results.is_empty() && matches!(format, ListFormat::Table | ListFormat::ItemTable) {
96 NO_PACKAGES_FOUND_MESSAGE.to_string()
97 } else {
98 format.render(results)
99 }
100}
101
102#[derive(clap::Parser, Debug)]
104pub struct PackageSearch {
105 #[clap(flatten)]
106 fmt: ListFormatOpts,
107
108 #[clap(flatten)]
109 env: WasmerEnv,
110
111 #[clap(long)]
113 owner: Option<String>,
114
115 #[clap(long)]
117 published_by: Option<String>,
118
119 #[clap(long, num_args = 0..=1, default_missing_value = "true", require_equals = true)]
122 curated: Option<bool>,
123
124 #[clap(long, num_args = 0..=1, default_missing_value = "true", require_equals = true)]
127 deployable: Option<bool>,
128
129 #[clap(long)]
131 has_bindings: bool,
132
133 #[clap(long)]
135 has_commands: bool,
136
137 #[clap(long)]
139 standalone: bool,
140
141 #[clap(long = "interface", value_name = "NAME")]
143 interfaces: Vec<String>,
144
145 #[clap(long)]
147 license: Option<String>,
148
149 #[clap(long, value_parser = clap::value_parser!(i32).range(0..))]
151 min_downloads: Option<i32>,
152
153 #[clap(long, value_parser = clap::value_parser!(i32).range(0..))]
155 min_likes: Option<i32>,
156
157 #[clap(long, value_parser = clap::value_parser!(i32).range(0..))]
159 min_size: Option<i32>,
160
161 #[clap(long, value_parser = parse_rfc3339)]
164 created_after: Option<DateTime>,
165
166 #[clap(long, value_parser = parse_rfc3339)]
168 created_before: Option<DateTime>,
169
170 #[clap(long, value_parser = parse_rfc3339)]
172 published_after: Option<DateTime>,
173
174 #[clap(long, value_parser = parse_rfc3339)]
176 published_before: Option<DateTime>,
177
178 #[clap(long, value_enum)]
180 published_within: Option<PublishedWithin>,
181
182 #[clap(long, value_enum, default_value = "published")]
184 order_by: OrderBy,
185
186 #[clap(long, value_enum, default_value = "desc")]
188 sort: Sort,
189
190 #[clap(long, default_value = "50")]
192 max: usize,
193
194 #[clap(default_value = "")]
196 query: String,
197}
198
199#[async_trait::async_trait]
200impl AsyncCliCommand for PackageSearch {
201 type Output = ();
202
203 async fn run_async(self) -> Result<(), anyhow::Error> {
204 let client = self.env.client_unauthennticated()?;
205
206 let with_interfaces =
207 (!self.interfaces.is_empty()).then(|| self.interfaces.into_iter().map(Some).collect());
208
209 let filter = PackagesFilter {
210 owner: self.owner,
211 published_by: self.published_by,
212 curated: self.curated,
213 deployable: self.deployable,
214 has_bindings: self.has_bindings.then_some(true),
215 has_commands: self.has_commands.then_some(true),
216 is_standalone: self.standalone.then_some(true),
217 with_interfaces,
218 license: self.license,
219 downloads: self.min_downloads.map(at_least),
220 likes: self.min_likes.map(at_least),
221 size: self.min_size.map(at_least),
222 created_after: self.created_after,
223 created_before: self.created_before,
224 last_published_after: self.published_after,
225 last_published_before: self.published_before,
226 publish_date: self.published_within.map(Into::into),
227 order_by: Some(self.order_by.into()),
228 sort_by: Some(self.sort.into()),
229 ..Default::default()
230 };
231
232 let results = wasmer_sdk::package::search::search_packages(
233 &client,
234 SearchOptions {
235 query: self.query,
236 filter,
237 limit: Some(self.max),
238 },
239 )
240 .await?;
241
242 println!(
243 "{}",
244 render_search_results(self.fmt.format, results.as_slice())
245 );
246
247 Ok(())
248 }
249}