summaryrefslogtreecommitdiff
path: root/src/http.rs
diff options
context:
space:
mode:
authorseth <[email protected]>2024-09-08 23:39:48 -0400
committerseth <[email protected]>2024-09-13 17:03:00 -0400
commitcc183fccca73df619c78dd0ca2567ac547c56ad2 (patch)
treea06a87049cd90e877e626b8ff31e27a373df8f39 /src/http.rs
feat: initial commit
Diffstat (limited to 'src/http.rs')
-rw-r--r--src/http.rs77
1 files changed, 77 insertions, 0 deletions
diff --git a/src/http.rs b/src/http.rs
new file mode 100644
index 0000000..1b37d4c
--- /dev/null
+++ b/src/http.rs
@@ -0,0 +1,77 @@
+/// HTTP interfaces
+use crate::Error;
+
+use anyhow::Result;
+use reqwest::{Method, StatusCode};
+use tracing::{event, instrument, Level};
+
+// https://nix.dev/manual/nix/2.24/store/store-path
+const STORE_DIRECTORY: &str = "/nix/store";
+const DIGEST_SIZE: usize = 32;
+
+pub type Client = reqwest::Client;
+
+pub trait Ext {
+ fn default() -> Self;
+ async fn has_store_path(&self, binary_cache_url: &str, store_path: &str) -> Result<bool>;
+}
+
+impl Ext for Client {
+ /// Create a client with our user agent
+ fn default() -> Self {
+ Client::builder()
+ .user_agent(concat!(
+ env!("CARGO_PKG_NAME"),
+ "/",
+ env!("CARGO_PKG_VERSION")
+ ))
+ .build()
+ .unwrap()
+ }
+
+ /// Check if a store path is available in the given binary cache
+ #[instrument(skip(self, binary_cache_url, store_path))]
+ async fn has_store_path(&self, binary_cache_url: &str, store_path: &str) -> Result<bool> {
+ let url = format!(
+ "{binary_cache_url}/{}.narinfo",
+ hash_from_store_path(store_path)
+ );
+
+ let request = self.request(Method::HEAD, url).build()?;
+ event!(
+ Level::TRACE,
+ "Checking for store path {store_path} in binary cache at {}",
+ request.url()
+ );
+ let response = self.execute(request).await?;
+
+ match response.status() {
+ StatusCode::OK => {
+ event!(
+ Level::TRACE,
+ "Found store path `{store_path}` in binary cache"
+ );
+ Ok(true)
+ }
+ StatusCode::NOT_FOUND => {
+ event!(
+ Level::TRACE,
+ "Did not find store path `{store_path}` in binary cache"
+ );
+ Ok(false)
+ }
+ status_code => Err(Error::HTTPFailed(status_code).into()),
+ }
+ }
+}
+
+/// Strip the <hash> from /nix/store/<hash>-<name>
+fn hash_from_store_path(store_path: &str) -> &str {
+ // Store paths will always start with the store directory, followed by a path separator. See
+ // the above link
+ let start_index = STORE_DIRECTORY.len() + 1;
+ // The next DIGEST_SIZE characters will then be the digest
+ let end_index = start_index + DIGEST_SIZE;
+
+ &store_path[start_index..end_index]
+}