normalize_package_index/index/
hex.rs1use super::{IndexError, PackageIndex, PackageMeta, VersionMeta};
12
13pub struct Hex;
15
16impl Hex {
17 const HEX_API: &'static str = "https://hex.pm/api";
19}
20
21impl PackageIndex for Hex {
22 fn ecosystem(&self) -> &'static str {
23 "hex"
24 }
25
26 fn display_name(&self) -> &'static str {
27 "Hex (Elixir/Erlang)"
28 }
29
30 fn fetch(&self, name: &str) -> Result<PackageMeta, IndexError> {
31 let url = format!("{}/packages/{}", Self::HEX_API, name);
32 let response: serde_json::Value = ureq::get(&url).call()?.into_json()?;
33
34 let meta = &response["meta"];
35 let latest_release = response["releases"].as_array().and_then(|r| r.first());
36
37 Ok(PackageMeta {
38 name: response["name"].as_str().unwrap_or(name).to_string(),
39 version: latest_release
40 .and_then(|r| r["version"].as_str())
41 .unwrap_or("unknown")
42 .to_string(),
43 description: meta["description"].as_str().map(String::from),
44 homepage: response["html_url"].as_str().map(String::from),
45 repository: meta["links"]["GitHub"]
46 .as_str()
47 .or_else(|| meta["links"]["Repository"].as_str())
48 .or_else(|| meta["links"]["Source"].as_str())
49 .map(String::from),
50 license: meta["licenses"]
51 .as_array()
52 .and_then(|l| l.first())
53 .and_then(|l| l.as_str())
54 .map(String::from),
55 binaries: Vec::new(),
56 keywords: Vec::new(), maintainers: meta["maintainers"]
58 .as_array()
59 .map(|m| {
60 m.iter()
61 .filter_map(|maint| maint["username"].as_str().map(String::from))
62 .collect()
63 })
64 .unwrap_or_default(),
65 published: latest_release
66 .and_then(|r| r["inserted_at"].as_str())
67 .map(String::from),
68 downloads: response["downloads"]["all"].as_u64(),
69 archive_url: latest_release
70 .and_then(|r| r["url"].as_str())
71 .map(String::from),
72 checksum: latest_release
73 .and_then(|r| r["checksum"].as_str())
74 .map(|h| format!("sha256:{}", h)),
75 extra: Default::default(),
76 })
77 }
78
79 fn fetch_versions(&self, name: &str) -> Result<Vec<VersionMeta>, IndexError> {
80 let url = format!("{}/packages/{}", Self::HEX_API, name);
81 let response: serde_json::Value = ureq::get(&url).call()?.into_json()?;
82
83 let releases = response["releases"]
84 .as_array()
85 .ok_or_else(|| IndexError::Parse("missing releases".into()))?;
86
87 Ok(releases
88 .iter()
89 .filter_map(|r| {
90 Some(VersionMeta {
91 version: r["version"].as_str()?.to_string(),
92 released: r["inserted_at"].as_str().map(String::from),
93 yanked: r["retired"].as_bool().unwrap_or(false),
94 })
95 })
96 .collect())
97 }
98
99 fn search(&self, query: &str) -> Result<Vec<PackageMeta>, IndexError> {
100 let url = format!("{}/packages?search={}", Self::HEX_API, query);
101 let response: serde_json::Value = ureq::get(&url).call()?.into_json()?;
102
103 let packages = response
104 .as_array()
105 .ok_or_else(|| IndexError::Parse("expected array".into()))?;
106
107 Ok(packages
108 .iter()
109 .filter_map(|pkg| {
110 let meta = &pkg["meta"];
111 let latest_release = pkg["releases"].as_array().and_then(|r| r.first());
112 Some(PackageMeta {
113 name: pkg["name"].as_str()?.to_string(),
114 version: latest_release
115 .and_then(|r| r["version"].as_str())
116 .unwrap_or("unknown")
117 .to_string(),
118 description: meta["description"].as_str().map(String::from),
119 homepage: pkg["html_url"].as_str().map(String::from),
120 repository: meta["links"]["GitHub"]
121 .as_str()
122 .or_else(|| meta["links"]["Repository"].as_str())
123 .map(String::from),
124 license: meta["licenses"]
125 .as_array()
126 .and_then(|l| l.first())
127 .and_then(|l| l.as_str())
128 .map(String::from),
129 binaries: Vec::new(),
130 keywords: Vec::new(),
131 maintainers: meta["maintainers"]
132 .as_array()
133 .map(|m| {
134 m.iter()
135 .filter_map(|maint| maint["username"].as_str().map(String::from))
136 .collect()
137 })
138 .unwrap_or_default(),
139 published: latest_release
140 .and_then(|r| r["inserted_at"].as_str())
141 .map(String::from),
142 downloads: pkg["downloads"]["all"].as_u64(),
143 archive_url: latest_release
144 .and_then(|r| r["url"].as_str())
145 .map(String::from),
146 checksum: latest_release
147 .and_then(|r| r["checksum"].as_str())
148 .map(|h| format!("sha256:{}", h)),
149 extra: Default::default(),
150 })
151 })
152 .collect())
153 }
154}