1use std::collections::HashSet;
2use std::path::PathBuf;
3use std::rc::Rc;
4
5use reqwest::Client;
6use semver::VersionReq;
7
8use crate::registry::*;
9
10mod package;
11mod registry;
12
13pub use package::{DepKind, Package, PackageVersion, Packages, Unit, Usage};
14
15type Purl = purl::GenericPurl<String>;
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
18pub enum RegistryCachePolicy {
19 PreferLocal,
20 #[default]
21 Refresh,
22 NoCache,
23}
24
25#[derive(Debug, Clone, Copy, Default)]
26pub struct Options {
27 pub registry_cache_policy: RegistryCachePolicy,
28}
29
30pub struct State {
31 client: Client,
32 root: Option<PathBuf>,
33 registry_cache_policy: RegistryCachePolicy,
34}
35
36impl State {
37 pub fn new(root: Option<PathBuf>, options: Options) -> Self {
38 let client = Client::builder()
39 .http2_adaptive_window(true)
40 .user_agent(concat!(
41 env!("CARGO_PKG_NAME"),
42 "/",
43 env!("CARGO_PKG_VERSION"),
44 " (+",
45 env!("CARGO_PKG_REPOSITORY"),
46 ")"
47 ))
48 .build()
49 .expect("failed to initialize HTTP client");
50 Self {
51 client,
52 root,
53 registry_cache_policy: options.registry_cache_policy,
54 }
55 }
56
57 pub fn client(&self) -> &Client {
58 &self.client
59 }
60
61 pub fn root(&self) -> Option<&PathBuf> {
62 self.root.as_ref()
63 }
64
65 pub fn registry_cache_policy(&self) -> RegistryCachePolicy {
66 self.registry_cache_policy
67 }
68}
69
70#[derive(Debug, thiserror::Error)]
71pub enum Error {
72 #[error("registry error: {0}")]
73 Registry(#[from] RegistryError),
74}
75
76pub struct CheckUpdates {
77 cargo: Registry,
78}
79
80impl CheckUpdates {
81 pub fn new(root: Option<PathBuf>) -> Self {
82 Self::with_options(root, Options::default())
83 }
84
85 pub fn with_options(root: Option<PathBuf>, options: Options) -> Self {
86 let state = Rc::new(State::new(root, options));
87 let cargo = CargoRegistry::new(state.clone());
88
89 Self {
90 cargo: cargo.into(),
91 }
92 }
93
94 pub async fn packages(&self) -> Result<Packages, Error> {
95 let mut res: Packages = Default::default();
96 let mut seen: HashSet<(Unit, String, String, DepKind)> = HashSet::new();
99 for package in self.cargo.packages().await? {
100 for usage in &package.usages {
101 if usage.req == VersionReq::STAR {
103 continue;
104 }
105 let key = (
106 usage.unit.clone(),
107 package.purl.name().to_string(),
108 usage.req.to_string(),
109 usage.kind,
110 );
111 if !seen.insert(key) {
112 continue;
113 }
114 res.entry(usage.unit.clone()).or_default().push((
115 usage.req.clone(),
116 usage.kind,
117 package.clone(),
118 ));
119 }
120 }
121 Ok(res)
122 }
123
124 pub fn update_versions<'a>(
126 &self,
127 packages: impl IntoIterator<Item = (&'a Usage, &'a Package, VersionReq)>,
128 ) -> Result<(), Error> {
129 self.cargo.update_versions(packages)?;
130 Ok(())
131 }
132}