Skip to main content

crates_io_api/
types.rs

1//! Types for the data that is available via the API.
2
3use chrono::{DateTime, NaiveDate, Utc};
4use serde_derive::*;
5use std::{collections::HashMap, fmt};
6
7/// A list of errors returned by the API.
8#[derive(Deserialize, Debug, Clone, PartialEq, Eq)]
9pub struct ApiErrors {
10    /// Individual errors.
11    pub errors: Vec<ApiError>,
12}
13
14/// An error returned by the API.
15#[derive(Deserialize, Debug, Clone, PartialEq, Eq)]
16pub struct ApiError {
17    /// Error message.
18    pub detail: Option<String>,
19}
20
21impl fmt::Display for ApiError {
22    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
23        write!(
24            f,
25            "{}",
26            self.detail.as_deref().unwrap_or("Unknown API Error")
27        )
28    }
29}
30
31/// Used to specify the sort behaviour of the `Client::crates()` method.
32#[derive(Debug, Clone, PartialEq, Eq)]
33pub enum Sort {
34    /// Sort alphabetically.
35    Alphabetical,
36    /// Sort by relevance (meaningless if used without a query).
37    Relevance,
38    /// Sort by downloads.
39    Downloads,
40    /// Sort by recent downloads
41    RecentDownloads,
42    /// Sort by recent updates
43    RecentUpdates,
44    /// Sort by new
45    NewlyAdded,
46}
47
48impl Sort {
49    pub(crate) fn to_str(&self) -> &str {
50        match self {
51            Self::Alphabetical => "alpha",
52            Self::Relevance => "relevance",
53            Self::Downloads => "downloads",
54            Self::RecentDownloads => "recent-downloads",
55            Self::RecentUpdates => "recent-updates",
56            Self::NewlyAdded => "new",
57        }
58    }
59}
60
61/// Options for the [crates]() method of the client.
62///
63/// Used to specify pagination, sorting and a query.
64#[derive(Clone, Debug)]
65pub struct CratesQuery {
66    /// Sort.
67    pub(crate) sort: Sort,
68    /// Number of items per page.
69    pub(crate) per_page: u64,
70    /// The page to fetch.
71    pub(crate) page: u64,
72    pub(crate) user_id: Option<u64>,
73    pub(crate) team_id: Option<u64>,
74    /// Crates.io category name.
75    /// See <https://crates.io/categories>
76    /// NOTE: requires lower-case dash-separated categories, not the pretty
77    /// titles visible in the listing linked above.
78    pub(crate) category: Option<String>,
79    /// Search query string.
80    pub(crate) search: Option<String>,
81    /// List of crate ids.
82    pub(crate) ids: Option<Vec<String>>,
83}
84
85impl CratesQuery {
86    pub(crate) fn build(&self, mut q: url::form_urlencoded::Serializer<'_, url::UrlQuery<'_>>) {
87        q.append_pair("page", &self.page.to_string());
88        q.append_pair("per_page", &self.per_page.to_string());
89        q.append_pair("sort", self.sort.to_str());
90        if let Some(id) = self.user_id {
91            q.append_pair("user_id", &id.to_string());
92        }
93        if let Some(id) = self.team_id {
94            q.append_pair("team_id", &id.to_string());
95        }
96        if let Some(search) = &self.search {
97            q.append_pair("q", search);
98        }
99        if let Some(cat) = &self.category {
100            q.append_pair("category", cat);
101        }
102        if let Some(ids) = &self.ids {
103            for id in ids {
104                q.append_pair("ids[]", id);
105            }
106        }
107    }
108}
109
110impl CratesQuery {
111    /// Construct a new [`CratesQueryBuilder`].
112    pub fn builder() -> CratesQueryBuilder {
113        CratesQueryBuilder::new()
114    }
115
116    /// Get a reference to the crate query's sort.
117    pub fn sort(&self) -> &Sort {
118        &self.sort
119    }
120
121    /// Set the crate query's sort.
122    pub fn set_sort(&mut self, sort: Sort) {
123        self.sort = sort;
124    }
125
126    /// Get the crate query's per page.
127    pub fn page_size(&self) -> u64 {
128        self.per_page
129    }
130
131    /// Set the crate query's per page.
132    pub fn set_page_size(&mut self, per_page: u64) {
133        self.per_page = per_page;
134    }
135
136    /// Get the crate query's page.
137    pub fn page(&self) -> u64 {
138        self.page
139    }
140
141    /// Set the crate query's page.
142    pub fn set_page(&mut self, page: u64) {
143        self.page = page;
144    }
145
146    /// Get the crate query's user id.
147    pub fn user_id(&self) -> Option<u64> {
148        self.user_id
149    }
150
151    /// Set the crate query's user id.
152    pub fn set_user_id(&mut self, user_id: Option<u64>) {
153        self.user_id = user_id;
154    }
155
156    /// Get the crate query's team id.
157    pub fn team_id(&self) -> Option<u64> {
158        self.team_id
159    }
160
161    /// Set the crate query's team id.
162    pub fn set_team_id(&mut self, team_id: Option<u64>) {
163        self.team_id = team_id;
164    }
165
166    /// Get a reference to the crate query's category.
167    pub fn category(&self) -> Option<&String> {
168        self.category.as_ref()
169    }
170
171    /// Set the crate query's category.
172    pub fn set_category(&mut self, category: Option<String>) {
173        self.category = category;
174    }
175
176    /// Get a reference to the crate query's search.
177    pub fn search(&self) -> Option<&String> {
178        self.search.as_ref()
179    }
180
181    /// Set the crate query's search.
182    pub fn set_search(&mut self, search: Option<String>) {
183        self.search = search;
184    }
185
186    /// Get a reference to the crate query's ids.
187    pub fn ids(&self) -> Option<&Vec<String>> {
188        self.ids.as_ref()
189    }
190
191    /// Set the crate query's ids.
192    pub fn set_ids(&mut self, ids: Option<Vec<String>>) {
193        self.ids = ids;
194    }
195}
196
197impl Default for CratesQuery {
198    fn default() -> Self {
199        Self {
200            sort: Sort::RecentUpdates,
201            per_page: 30,
202            page: 1,
203            user_id: None,
204            team_id: None,
205            category: None,
206            search: None,
207            ids: None,
208        }
209    }
210}
211
212/// Builder that enables easy construction of a [`CratesQuery`].
213pub struct CratesQueryBuilder {
214    query: CratesQuery,
215}
216
217impl CratesQueryBuilder {
218    /// Construct a new builder.
219    #[must_use]
220    pub fn new() -> Self {
221        Self {
222            query: CratesQuery::default(),
223        }
224    }
225
226    /// Set the sorting method.
227    #[must_use]
228    pub fn sort(mut self, sort: Sort) -> Self {
229        self.query.sort = sort;
230        self
231    }
232
233    /// Set the page.
234    #[must_use]
235    pub fn page(mut self, page: u64) -> Self {
236        self.query.page = page;
237        self
238    }
239
240    /// Set the page size.
241    #[must_use]
242    pub fn page_size(mut self, size: u64) -> Self {
243        self.query.per_page = size;
244        self
245    }
246
247    /// Filter by a user id.
248    #[must_use]
249    pub fn user_id(mut self, user_id: u64) -> Self {
250        self.query.user_id = Some(user_id);
251        self
252    }
253
254    /// Filter by a team id.
255    #[must_use]
256    pub fn team_id(mut self, team_id: u64) -> Self {
257        self.query.team_id = Some(team_id);
258        self
259    }
260
261    /// Crates.io category name.
262    /// See <https://crates.io/categories>
263    /// NOTE: requires lower-case dash-separated categories, not the pretty
264    /// titles visible in the listing linked above.
265    #[must_use]
266    pub fn category(mut self, category: impl Into<String>) -> Self {
267        self.query.category = Some(category.into());
268        self
269    }
270
271    /// Search term.
272    #[must_use]
273    pub fn search(mut self, search: impl Into<String>) -> Self {
274        self.query.search = Some(search.into());
275        self
276    }
277
278    /// List of crate ids.
279    #[must_use]
280    pub fn ids(mut self, ids: Vec<String>) -> Self {
281        self.query.ids = Some(ids);
282        self
283    }
284
285    /// Finalize the builder into a usable [`CratesQuery`].
286    #[must_use]
287    pub fn build(self) -> CratesQuery {
288        self.query
289    }
290}
291
292impl Default for CratesQueryBuilder {
293    fn default() -> Self {
294        Self::new()
295    }
296}
297
298/// Pagination information.
299#[derive(Serialize, Deserialize, Debug, Clone)]
300pub struct Meta {
301    /// The total amount of results.
302    pub total: u64,
303}
304
305/// Links to individual API endpoints that provide crate details.
306#[derive(Serialize, Deserialize, Debug, Clone)]
307#[allow(missing_docs)]
308pub struct CrateLinks {
309    pub owner_team: String,
310    pub owner_user: String,
311    pub owners: String,
312    pub reverse_dependencies: String,
313    pub version_downloads: String,
314    pub versions: Option<String>,
315}
316
317/// A Rust crate published to crates.io.
318#[derive(Serialize, Deserialize, Debug, Clone)]
319#[allow(missing_docs)]
320pub struct Crate {
321    pub id: String,
322    pub name: String,
323    pub description: Option<String>,
324    // FIXME: Remove on next breaking version bump.
325    #[deprecated(
326        since = "0.8.1",
327        note = "This field is always empty. The license is only available on a specific `Version` of a crate or on `FullCrate`. This field will be removed in the next minor version bump."
328    )]
329    pub license: Option<String>,
330    pub documentation: Option<String>,
331    pub homepage: Option<String>,
332    pub repository: Option<String>,
333    // TODO: determine badge format.
334    // pub badges: Vec<??>,
335    pub downloads: u64,
336    pub recent_downloads: Option<u64>,
337    /// NOTE: not set if the crate was loaded via a list query.
338    pub categories: Option<Vec<String>>,
339    /// NOTE: not set if the crate was loaded via a list query.
340    pub keywords: Option<Vec<String>>,
341    pub versions: Option<Vec<u64>>,
342    pub max_version: String,
343    pub max_stable_version: Option<String>,
344    pub links: CrateLinks,
345    pub created_at: DateTime<Utc>,
346    pub updated_at: DateTime<Utc>,
347    pub exact_match: Option<bool>,
348}
349
350/// Full data for a crate listing.
351#[derive(Serialize, Deserialize, Debug, Clone)]
352#[allow(missing_docs)]
353pub struct CratesPage {
354    pub crates: Vec<Crate>,
355    #[serde(default)]
356    pub versions: Vec<Version>,
357    #[serde(default)]
358    pub keywords: Vec<Keyword>,
359    #[serde(default)]
360    pub categories: Vec<Category>,
361    pub meta: Meta,
362}
363
364/// Links to API endpoints providing extra data for a crate version.
365#[derive(Serialize, Deserialize, Debug, Clone)]
366#[allow(missing_docs)]
367pub struct VersionLinks {
368    #[deprecated(
369        since = "0.7.1",
370        note = "This field was removed from the API and will always be empty. Will be removed in 0.8.0."
371    )]
372    #[serde(default)]
373    pub authors: String,
374    pub dependencies: String,
375    pub version_downloads: String,
376}
377
378/// Changes made to a create [`Version`]
379#[derive(Serialize, Deserialize, Debug, Clone)]
380#[allow(missing_docs)]
381pub struct AuditAction {
382    /// publish, yank, unyank
383    action: String,
384    time: DateTime<Utc>,
385    user: User,
386}
387
388/// A [`Crate`] version.
389#[derive(Serialize, Deserialize, Debug, Clone)]
390#[allow(missing_docs)]
391pub struct Version {
392    #[serde(rename = "crate")]
393    pub crate_name: String,
394    pub created_at: DateTime<Utc>,
395    pub updated_at: DateTime<Utc>,
396    pub dl_path: String,
397    pub downloads: u64,
398    pub features: HashMap<String, Vec<String>>,
399    pub id: u64,
400    pub num: String,
401    pub yanked: bool,
402    pub license: Option<String>,
403    pub readme_path: Option<String>,
404    pub links: VersionLinks,
405    pub crate_size: Option<u64>,
406    pub published_by: Option<User>,
407    pub rust_version: Option<String>,
408    #[serde(default)]
409    pub audit_actions: Vec<AuditAction>,
410    pub checksum: String,
411}
412
413/// A crate category.
414#[derive(Serialize, Deserialize, Debug, Clone)]
415#[allow(missing_docs)]
416pub struct Category {
417    pub category: String,
418    pub crates_cnt: u64,
419    pub created_at: DateTime<Utc>,
420    pub description: String,
421    pub id: String,
422    pub slug: String,
423}
424
425/// A keyword available on crates.io.
426#[derive(Serialize, Deserialize, Debug, Clone)]
427#[allow(missing_docs)]
428pub struct Keyword {
429    pub id: String,
430    pub keyword: String,
431    pub crates_cnt: u64,
432    pub created_at: DateTime<Utc>,
433}
434
435/// Full data for a crate.
436#[derive(Serialize, Deserialize, Debug, Clone)]
437#[allow(missing_docs)]
438pub struct CrateResponse {
439    pub categories: Vec<Category>,
440    #[serde(rename = "crate")]
441    pub crate_data: Crate,
442    pub keywords: Vec<Keyword>,
443    pub versions: Vec<Version>,
444}
445
446/// Summary for crates.io.
447#[derive(Serialize, Deserialize, Debug, Clone)]
448#[allow(missing_docs)]
449pub struct Summary {
450    pub just_updated: Vec<Crate>,
451    pub most_downloaded: Vec<Crate>,
452    pub new_crates: Vec<Crate>,
453    pub most_recently_downloaded: Vec<Crate>,
454    pub num_crates: u64,
455    pub num_downloads: u64,
456    pub popular_categories: Vec<Category>,
457    pub popular_keywords: Vec<Keyword>,
458}
459
460/// Download data for a single crate version.
461#[derive(Serialize, Deserialize, Debug, Clone)]
462#[allow(missing_docs)]
463pub struct VersionDownloads {
464    pub date: NaiveDate,
465    pub downloads: u64,
466    pub version: u64,
467}
468
469/// Crate downloads that don't fit a particular date.
470/// Only required for old download data.
471#[derive(Serialize, Deserialize, Debug, Clone)]
472#[allow(missing_docs)]
473pub struct ExtraDownloads {
474    pub date: NaiveDate,
475    pub downloads: u64,
476}
477
478/// Additional data for crate downloads.
479#[derive(Serialize, Deserialize, Debug, Clone)]
480#[allow(missing_docs)]
481pub struct CrateDownloadsMeta {
482    pub extra_downloads: Vec<ExtraDownloads>,
483}
484
485/// Download data for all versions of a [`Crate`].
486#[derive(Serialize, Deserialize, Debug, Clone)]
487#[allow(missing_docs)]
488pub struct CrateDownloads {
489    pub version_downloads: Vec<VersionDownloads>,
490    pub meta: CrateDownloadsMeta,
491}
492
493/// A crates.io user.
494#[derive(Serialize, Deserialize, Debug, Clone)]
495#[allow(missing_docs)]
496pub struct User {
497    pub avatar: Option<String>,
498    pub email: Option<String>,
499    pub id: u64,
500    pub kind: Option<String>,
501    pub login: String,
502    pub name: Option<String>,
503    pub url: String,
504}
505
506/// Additional crate author metadata.
507#[derive(Serialize, Deserialize, Debug, Clone)]
508#[allow(missing_docs)]
509pub struct AuthorsMeta {
510    pub names: Vec<String>,
511}
512
513/// API Response for authors data.
514#[derive(Serialize, Deserialize, Debug, Clone)]
515#[allow(missing_docs)]
516pub(crate) struct AuthorsResponse {
517    pub meta: AuthorsMeta,
518}
519
520/// Crate author names.
521#[derive(Serialize, Deserialize, Debug, Clone)]
522#[allow(missing_docs)]
523pub struct Authors {
524    pub names: Vec<String>,
525}
526
527/// Crate owners.
528#[derive(Serialize, Deserialize, Debug, Clone)]
529#[allow(missing_docs)]
530pub struct Owners {
531    pub users: Vec<User>,
532}
533
534/// A crate dependency.
535/// Specifies the crate and features.
536#[derive(Serialize, Deserialize, Debug, Clone)]
537#[allow(missing_docs)]
538pub struct Dependency {
539    pub crate_id: String,
540    pub default_features: bool,
541    pub downloads: u64,
542    pub features: Vec<String>,
543    pub id: u64,
544    pub kind: String,
545    pub optional: bool,
546    pub req: String,
547    pub target: Option<String>,
548    pub version_id: u64,
549}
550
551/// List of dependencies of a crate.
552#[derive(Serialize, Deserialize, Debug, Clone)]
553#[allow(missing_docs)]
554pub struct Dependencies {
555    pub dependencies: Vec<Dependency>,
556}
557
558/// Single reverse dependency (aka a dependent) of a crate.
559#[derive(Serialize, Deserialize, Debug, Clone)]
560#[allow(missing_docs)]
561pub struct ReverseDependency {
562    pub crate_version: Version,
563    pub dependency: Dependency,
564}
565
566// This is how reverse dependencies are received
567#[derive(Serialize, Deserialize, Debug, Clone)]
568pub(super) struct ReverseDependenciesAsReceived {
569    pub dependencies: Vec<Dependency>,
570    pub versions: Vec<Version>,
571    pub meta: Meta,
572}
573
574/// Full list of reverse dependencies for a crate (version).
575#[derive(Serialize, Deserialize, Debug, Clone)]
576#[allow(missing_docs)]
577pub struct ReverseDependencies {
578    pub dependencies: Vec<ReverseDependency>,
579    pub meta: Meta,
580}
581
582impl ReverseDependencies {
583    /// Fills the dependencies field from a ReverseDependenciesAsReceived struct.
584    pub(crate) fn extend(&mut self, rdeps: ReverseDependenciesAsReceived) {
585        for d in rdeps.dependencies {
586            for v in &rdeps.versions {
587                if v.id == d.version_id {
588                    // Right now it iterates over the full vector for each vector element.
589                    // For large vectors, it may be faster to remove each matched element
590                    // using the drain_filter() method once it's stabilized:
591                    // https://doc.rust-lang.org/nightly/std/vec/struct.Vec.html#method.drain_filter
592                    self.dependencies.push(ReverseDependency {
593                        crate_version: v.clone(),
594                        dependency: d.clone(),
595                    });
596                }
597            }
598        }
599    }
600}
601
602/// Complete information for a crate version.
603#[derive(Serialize, Deserialize, Debug, Clone)]
604#[allow(missing_docs)]
605pub struct FullVersion {
606    #[serde(rename = "crate")]
607    pub crate_name: String,
608    pub created_at: DateTime<Utc>,
609    pub updated_at: DateTime<Utc>,
610    pub dl_path: String,
611    pub downloads: u64,
612    pub features: HashMap<String, Vec<String>>,
613    pub id: u64,
614    pub num: String,
615    pub yanked: bool,
616    pub license: Option<String>,
617    pub readme_path: Option<String>,
618    pub links: VersionLinks,
619    pub crate_size: Option<u64>,
620    pub published_by: Option<User>,
621    pub rust_version: Option<String>,
622    #[serde(default)]
623    pub audit_actions: Vec<AuditAction>,
624
625    pub author_names: Vec<String>,
626    pub dependencies: Vec<Dependency>,
627    pub checksum: String,
628}
629
630impl FullVersion {
631    /// Creates a [`FullVersion`] from a [`Version`], author names, and dependencies.
632    pub fn from_parts(version: Version, authors: Authors, dependencies: Vec<Dependency>) -> Self {
633        FullVersion {
634            crate_name: version.crate_name,
635            created_at: version.created_at,
636            updated_at: version.updated_at,
637            dl_path: version.dl_path,
638            downloads: version.downloads,
639            features: version.features,
640            id: version.id,
641            num: version.num,
642            yanked: version.yanked,
643            license: version.license,
644            links: version.links,
645            readme_path: version.readme_path,
646            crate_size: version.crate_size,
647            published_by: version.published_by,
648            rust_version: version.rust_version,
649            audit_actions: version.audit_actions,
650
651            author_names: authors.names,
652            dependencies,
653            checksum: version.checksum,
654        }
655    }
656}
657
658/// Complete information for a crate.
659#[derive(Serialize, Deserialize, Debug, Clone)]
660#[allow(missing_docs)]
661pub struct FullCrate {
662    pub id: String,
663    pub name: String,
664    pub description: Option<String>,
665    pub license: Option<String>,
666    pub documentation: Option<String>,
667    pub homepage: Option<String>,
668    pub repository: Option<String>,
669    pub total_downloads: u64,
670    pub recent_downloads: Option<u64>,
671    pub max_version: String,
672    pub max_stable_version: Option<String>,
673    pub created_at: DateTime<Utc>,
674    pub updated_at: DateTime<Utc>,
675
676    pub categories: Vec<Category>,
677    pub keywords: Vec<Keyword>,
678    pub downloads: CrateDownloads,
679    pub owners: Vec<User>,
680    pub reverse_dependencies: ReverseDependencies,
681
682    pub versions: Vec<FullVersion>,
683}
684
685#[derive(Serialize, Deserialize, Debug, Clone)]
686pub(crate) struct UserResponse {
687    pub user: User,
688}