1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
//! Types for the data that is available via the API.

use chrono::{DateTime, NaiveDate, Utc};
use serde_derive::*;
use std::collections::HashMap;

/// Used to specify the sort behaviour of the `Client::crates()` method.
#[derive(Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct ApiErrors {
    /// Individual errors.
    pub errors: Vec<ApiError>,
}

/// Used to specify the sort behaviour of the `Client::crates()` method.
#[derive(Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct ApiError {
    /// Error message.
    pub detail: Option<String>,
}

impl std::fmt::Display for ApiError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "{}",
            self.detail.as_deref().unwrap_or("Unknown API Error")
        )
    }
}

/// Used to specify the sort behaviour of the `Client::crates()` method.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Sort {
    /// Sort alphabetically.
    Alphabetical,
    /// Sort by relevance (meaningless if used without a query).
    Relevance,
    /// Sort by downloads.
    Downloads,
    /// Sort by recent downloads
    RecentDownloads,
    /// Sort by recent updates
    RecentUpdates,
    /// Sort by new
    NewlyAdded,
}

impl Sort {
    pub(crate) fn to_str(&self) -> &str {
        match self {
            Self::Alphabetical => "alpha",
            Self::Relevance => "relevance",
            Self::Downloads => "downloads",
            Self::RecentDownloads => "recent-downloads",
            Self::RecentUpdates => "recent-updates",
            Self::NewlyAdded => "new",
        }
    }
}

/// Options for the [crates]() method of the client.
///
/// Used to specify pagination, sorting and a query.
#[derive(Clone, Debug)]
pub struct CratesQuery {
    /// Sort.
    pub(crate) sort: Sort,
    /// Number of items per page.
    pub(crate) per_page: u64,
    /// The page to fetch.
    pub(crate) page: u64,
    pub(crate) user_id: Option<u64>,
    /// Crates.io category name.
    /// See <https://crates.io/categories>
    /// NOTE: requires lower-case dash-separated categories, not the pretty
    /// titles visible in the listing linked above.
    pub(crate) category: Option<String>,
    /// Search query string.
    pub(crate) search: Option<String>,
}

impl CratesQuery {
    pub(crate) fn build(&self, mut q: url::form_urlencoded::Serializer<'_, url::UrlQuery<'_>>) {
        q.append_pair("page", &self.page.to_string());
        q.append_pair("per_page", &self.per_page.to_string());
        q.append_pair("sort", self.sort.to_str());
        if let Some(id) = self.user_id {
            q.append_pair("user_id", &id.to_string());
        }
        if let Some(search) = &self.search {
            q.append_pair("q", search);
        }
        if let Some(cat) = &self.category {
            q.append_pair("category", cat);
        }
    }
}

impl CratesQuery {
    /// Construct a new [`CratesQueryBuilder`].
    pub fn builder() -> CratesQueryBuilder {
        CratesQueryBuilder::new()
    }

    /// Get a reference to the crate query's sort.
    pub fn sort(&self) -> &Sort {
        &self.sort
    }

    /// Set the crate query's sort.
    pub fn set_sort(&mut self, sort: Sort) {
        self.sort = sort;
    }

    /// Get the crate query's per page.
    pub fn page_size(&self) -> u64 {
        self.per_page
    }

    /// Set the crate query's per page.
    pub fn set_page_size(&mut self, per_page: u64) {
        self.per_page = per_page;
    }

    /// Get the crate query's page.
    pub fn page(&self) -> u64 {
        self.page
    }

    /// Set the crate query's page.
    pub fn set_page(&mut self, page: u64) {
        self.page = page;
    }

    /// Get the crate query's user id.
    pub fn user_id(&self) -> Option<u64> {
        self.user_id
    }

    /// Set the crate query's user id.
    pub fn set_user_id(&mut self, user_id: Option<u64>) {
        self.user_id = user_id;
    }

    /// Get a reference to the crate query's category.
    pub fn category(&self) -> Option<&String> {
        self.category.as_ref()
    }

    /// Set the crate query's category.
    pub fn set_category(&mut self, category: Option<String>) {
        self.category = category;
    }

    /// Get a reference to the crate query's search.
    pub fn search(&self) -> Option<&String> {
        self.search.as_ref()
    }

    /// Set the crate query's search.
    pub fn set_search(&mut self, search: Option<String>) {
        self.search = search;
    }
}

impl Default for CratesQuery {
    fn default() -> Self {
        Self {
            sort: Sort::RecentUpdates,
            per_page: 30,
            page: 1,
            user_id: None,
            category: None,
            search: None,
        }
    }
}

/// Builder that enables easy construction of a [`CratesQuery`].
pub struct CratesQueryBuilder {
    query: CratesQuery,
}

impl CratesQueryBuilder {
    /// Construct a new builder.
    #[must_use]
    pub fn new() -> Self {
        Self {
            query: CratesQuery::default(),
        }
    }

    /// Set the sorting method.
    #[must_use]
    pub fn sort(mut self, sort: Sort) -> Self {
        self.query.sort = sort;
        self
    }

    /// Set the page.
    #[must_use]
    pub fn page(mut self, page: u64) -> Self {
        self.query.page = page;
        self
    }

    /// Set the page size.
    #[must_use]
    pub fn page_size(mut self, size: u64) -> Self {
        self.query.per_page = size;
        self
    }

    /// Filter by a user id.
    #[must_use]
    pub fn user_id(mut self, user_id: u64) -> Self {
        self.query.user_id = Some(user_id);
        self
    }

    /// Crates.io category name.
    /// See <https://crates.io/categories>
    /// NOTE: requires lower-case dash-separated categories, not the pretty
    /// titles visible in the listing linked above.
    #[must_use]
    pub fn category(mut self, category: impl Into<String>) -> Self {
        self.query.category = Some(category.into());
        self
    }

    /// Search term.
    #[must_use]
    pub fn search(mut self, search: impl Into<String>) -> Self {
        self.query.search = Some(search.into());
        self
    }

    /// Finalize the builder into a usable [`CratesQuery`].
    #[must_use]
    pub fn build(self) -> CratesQuery {
        self.query
    }
}

impl Default for CratesQueryBuilder {
    fn default() -> Self {
        Self::new()
    }
}

/// Pagination information.
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Meta {
    /// The total amount of results.
    pub total: u64,
}

/// Links to individual API endpoints that provide crate details.
#[derive(Serialize, Deserialize, Debug, Clone)]
#[allow(missing_docs)]
pub struct CrateLinks {
    pub owner_team: String,
    pub owner_user: String,
    pub owners: String,
    pub reverse_dependencies: String,
    pub version_downloads: String,
    pub versions: Option<String>,
}

/// A Rust crate published to crates.io.
#[derive(Serialize, Deserialize, Debug, Clone)]
#[allow(missing_docs)]
pub struct Crate {
    pub id: String,
    pub name: String,
    pub description: Option<String>,
    // FIXME: Remove on next breaking version bump.
    #[deprecated(
        since = "0.8.1",
        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."
    )]
    pub license: Option<String>,
    pub documentation: Option<String>,
    pub homepage: Option<String>,
    pub repository: Option<String>,
    // TODO: determine badge format.
    // pub badges: Vec<??>,
    pub downloads: u64,
    pub recent_downloads: Option<u64>,
    /// NOTE: not set if the crate was loaded via a list query.
    pub categories: Option<Vec<String>>,
    /// NOTE: not set if the crate was loaded via a list query.
    pub keywords: Option<Vec<String>>,
    pub versions: Option<Vec<u64>>,
    pub max_version: String,
    pub max_stable_version: Option<String>,
    pub links: CrateLinks,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
    pub exact_match: Option<bool>,
}

/// Full data for a crate listing.
#[derive(Serialize, Deserialize, Debug, Clone)]
#[allow(missing_docs)]
pub struct CratesPage {
    pub crates: Vec<Crate>,
    #[serde(default)]
    pub versions: Vec<Version>,
    #[serde(default)]
    pub keywords: Vec<Keyword>,
    #[serde(default)]
    pub categories: Vec<Category>,
    pub meta: Meta,
}

/// Links to API endpoints providing extra data for a crate version.
#[derive(Serialize, Deserialize, Debug, Clone)]
#[allow(missing_docs)]
pub struct VersionLinks {
    #[deprecated(
        since = "0.7.1",
        note = "This field was removed from the API and will always be empty. Will be removed in 0.8.0."
    )]
    #[serde(default)]
    pub authors: String,
    pub dependencies: String,
    pub version_downloads: String,
}

/// Changes made to a create [`Version`]
#[derive(Serialize, Deserialize, Debug, Clone)]
#[allow(missing_docs)]
pub struct AuditAction {
    /// publish, yank, unyank
    action: String,
    time: DateTime<Utc>,
    user: User,
}

/// A [`Crate`] version.
#[derive(Serialize, Deserialize, Debug, Clone)]
#[allow(missing_docs)]
pub struct Version {
    #[serde(rename = "crate")]
    pub crate_name: String,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
    pub dl_path: String,
    pub downloads: u64,
    pub features: HashMap<String, Vec<String>>,
    pub id: u64,
    pub num: String,
    pub yanked: bool,
    pub license: Option<String>,
    pub readme_path: Option<String>,
    pub links: VersionLinks,
    pub crate_size: Option<u64>,
    pub published_by: Option<User>,
    pub rust_version: Option<String>,
    #[serde(default)]
    pub audit_actions: Vec<AuditAction>,
}

/// A crate category.
#[derive(Serialize, Deserialize, Debug, Clone)]
#[allow(missing_docs)]
pub struct Category {
    pub category: String,
    pub crates_cnt: u64,
    pub created_at: DateTime<Utc>,
    pub description: String,
    pub id: String,
    pub slug: String,
}

/// A keyword available on crates.io.
#[derive(Serialize, Deserialize, Debug, Clone)]
#[allow(missing_docs)]
pub struct Keyword {
    pub id: String,
    pub keyword: String,
    pub crates_cnt: u64,
    pub created_at: DateTime<Utc>,
}

/// Full data for a crate.
#[derive(Serialize, Deserialize, Debug, Clone)]
#[allow(missing_docs)]
pub struct CrateResponse {
    pub categories: Vec<Category>,
    #[serde(rename = "crate")]
    pub crate_data: Crate,
    pub keywords: Vec<Keyword>,
    pub versions: Vec<Version>,
}

/// Summary for crates.io.
#[derive(Serialize, Deserialize, Debug, Clone)]
#[allow(missing_docs)]
pub struct Summary {
    pub just_updated: Vec<Crate>,
    pub most_downloaded: Vec<Crate>,
    pub new_crates: Vec<Crate>,
    pub most_recently_downloaded: Vec<Crate>,
    pub num_crates: u64,
    pub num_downloads: u64,
    pub popular_categories: Vec<Category>,
    pub popular_keywords: Vec<Keyword>,
}

/// Download data for a single crate version.
#[derive(Serialize, Deserialize, Debug, Clone)]
#[allow(missing_docs)]
pub struct VersionDownloads {
    pub date: NaiveDate,
    pub downloads: u64,
    pub version: u64,
}

/// Crate downloads that don't fit a particular date.
/// Only required for old download data.
#[derive(Serialize, Deserialize, Debug, Clone)]
#[allow(missing_docs)]
pub struct ExtraDownloads {
    pub date: NaiveDate,
    pub downloads: u64,
}

/// Additional data for crate downloads.
#[derive(Serialize, Deserialize, Debug, Clone)]
#[allow(missing_docs)]
pub struct CrateDownloadsMeta {
    pub extra_downloads: Vec<ExtraDownloads>,
}

/// Download data for all versions of a [`Crate`].
#[derive(Serialize, Deserialize, Debug, Clone)]
#[allow(missing_docs)]
pub struct CrateDownloads {
    pub version_downloads: Vec<VersionDownloads>,
    pub meta: CrateDownloadsMeta,
}

/// A crates.io user.
#[derive(Serialize, Deserialize, Debug, Clone)]
#[allow(missing_docs)]
pub struct User {
    pub avatar: Option<String>,
    pub email: Option<String>,
    pub id: u64,
    pub kind: Option<String>,
    pub login: String,
    pub name: Option<String>,
    pub url: String,
}

/// Additional crate author metadata.
#[derive(Serialize, Deserialize, Debug, Clone)]
#[allow(missing_docs)]
pub struct AuthorsMeta {
    pub names: Vec<String>,
}

/// API Response for authors data.
#[derive(Serialize, Deserialize, Debug, Clone)]
#[allow(missing_docs)]
pub(crate) struct AuthorsResponse {
    pub meta: AuthorsMeta,
}

/// Crate author names.
#[derive(Serialize, Deserialize, Debug, Clone)]
#[allow(missing_docs)]
pub struct Authors {
    pub names: Vec<String>,
}

/// Crate owners.
#[derive(Serialize, Deserialize, Debug, Clone)]
#[allow(missing_docs)]
pub struct Owners {
    pub users: Vec<User>,
}

/// A crate dependency.
/// Specifies the crate and features.
#[derive(Serialize, Deserialize, Debug, Clone)]
#[allow(missing_docs)]
pub struct Dependency {
    pub crate_id: String,
    pub default_features: bool,
    pub downloads: u64,
    pub features: Vec<String>,
    pub id: u64,
    pub kind: String,
    pub optional: bool,
    pub req: String,
    pub target: Option<String>,
    pub version_id: u64,
}

/// List of dependencies of a crate.
#[derive(Serialize, Deserialize, Debug, Clone)]
#[allow(missing_docs)]
pub struct Dependencies {
    pub dependencies: Vec<Dependency>,
}

/// Single reverse dependency (aka a dependent) of a crate.
#[derive(Serialize, Deserialize, Debug, Clone)]
#[allow(missing_docs)]
pub struct ReverseDependency {
    pub crate_version: Version,
    pub dependency: Dependency,
}

// This is how reverse dependencies are received
#[derive(Serialize, Deserialize, Debug, Clone)]
pub(super) struct ReverseDependenciesAsReceived {
    pub dependencies: Vec<Dependency>,
    pub versions: Vec<Version>,
    pub meta: Meta,
}

/// Full list of reverse dependencies for a crate (version).
#[derive(Serialize, Deserialize, Debug, Clone)]
#[allow(missing_docs)]
pub struct ReverseDependencies {
    pub dependencies: Vec<ReverseDependency>,
    pub meta: Meta,
}

impl ReverseDependencies {
    /// Fills the dependencies field from a ReverseDependenciesAsReceived struct.
    pub(crate) fn extend(&mut self, rdeps: ReverseDependenciesAsReceived) {
        for d in rdeps.dependencies {
            for v in &rdeps.versions {
                if v.id == d.version_id {
                    // Right now it iterates over the full vector for each vector element.
                    // For large vectors, it may be faster to remove each matched element
                    // using the drain_filter() method once it's stabilized:
                    // https://doc.rust-lang.org/nightly/std/vec/struct.Vec.html#method.drain_filter
                    self.dependencies.push(ReverseDependency {
                        crate_version: v.clone(),
                        dependency: d.clone(),
                    });
                }
            }
        }
    }
}

/// Complete information for a crate version.
#[derive(Serialize, Deserialize, Debug, Clone)]
#[allow(missing_docs)]
pub struct FullVersion {
    #[serde(rename = "crate")]
    pub crate_name: String,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
    pub dl_path: String,
    pub downloads: u64,
    pub features: HashMap<String, Vec<String>>,
    pub id: u64,
    pub num: String,
    pub yanked: bool,
    pub license: Option<String>,
    pub readme_path: Option<String>,
    pub links: VersionLinks,
    pub crate_size: Option<u64>,
    pub published_by: Option<User>,
    pub rust_version: Option<String>,
    #[serde(default)]
    pub audit_actions: Vec<AuditAction>,

    pub author_names: Vec<String>,
    pub dependencies: Vec<Dependency>,
}

impl FullVersion {
    /// Creates a [`FullVersion`] from a [`Version`], author names, and dependencies.
    pub fn from_parts(version: Version, authors: Authors, dependencies: Vec<Dependency>) -> Self {
        FullVersion {
            crate_name: version.crate_name,
            created_at: version.created_at,
            updated_at: version.updated_at,
            dl_path: version.dl_path,
            downloads: version.downloads,
            features: version.features,
            id: version.id,
            num: version.num,
            yanked: version.yanked,
            license: version.license,
            links: version.links,
            readme_path: version.readme_path,
            crate_size: version.crate_size,
            published_by: version.published_by,
            rust_version: version.rust_version,
            audit_actions: version.audit_actions,

            author_names: authors.names,
            dependencies,
        }
    }
}

/// Complete information for a crate.
#[derive(Serialize, Deserialize, Debug, Clone)]
#[allow(missing_docs)]
pub struct FullCrate {
    pub id: String,
    pub name: String,
    pub description: Option<String>,
    pub license: Option<String>,
    pub documentation: Option<String>,
    pub homepage: Option<String>,
    pub repository: Option<String>,
    pub total_downloads: u64,
    pub recent_downloads: Option<u64>,
    pub max_version: String,
    pub max_stable_version: Option<String>,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,

    pub categories: Vec<Category>,
    pub keywords: Vec<Keyword>,
    pub downloads: CrateDownloads,
    pub owners: Vec<User>,
    pub reverse_dependencies: ReverseDependencies,

    pub versions: Vec<FullVersion>,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub(crate) struct UserResponse {
    pub user: User,
}