summaryrefslogtreecommitdiff
path: root/src/http.rs
blob: 1b37d4c1f91fcfddf01968dc5f21989528dbd589 (plain)
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
/// 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]
}