use hmac::{Hmac, Mac};
use http::Request;
use sha2::Sha256;
use thiserror::Error;
use tower::filter::Predicate;
#[derive(Debug, Clone)]
pub struct HmacQueryParamValidator {
	pub key: String,
}
#[derive(Error, Debug)]
pub enum HmacQueryParamError {
	#[error("Request did not have a query string")]
	NoQueryString,
	#[error("Request did not have an HMAC query param")]
	QueryStringButNoHmac,
	#[error("Hash key not appropriately set")]
	HashKeyNotSet,
	#[error("Failed decoding HMAC as hex string")]
	HexDecodingError,
	#[error("Computed hash did not match provided hash")]
	HashVerificationFailed,
}
impl<T> Predicate<Request<T>> for HmacQueryParamValidator {
	type Request = http::Request<T>;
	fn check(&mut self, request: Request<T>) -> Result<Self::Request, tower::BoxError> {
		let query = request.uri().query().ok_or(HmacQueryParamError::NoQueryString)?;
		let (hmac, params): (Vec<_>, Vec<_>) =
			form_urlencoded::parse(query.as_bytes()).partition(|(key, _)| key == "hmac");
		let hmac = &hmac.first().ok_or(HmacQueryParamError::QueryStringButNoHmac)?.1;
		let query_string_without_hmac = form_urlencoded::Serializer::new(String::new())
			.extend_pairs(params)
			.finish();
		let mut hasher =
			Hmac::<Sha256>::new_from_slice(self.key.as_bytes()).map_err(|_| HmacQueryParamError::HashKeyNotSet)?;
		hasher.update(&query_string_without_hmac.into_bytes());
		let hmac_bytes = hex::decode(hmac.as_bytes()).map_err(|_| HmacQueryParamError::HexDecodingError)?;
		hasher
			.verify(hmac_bytes.as_slice().into())
			.map_err(|_| HmacQueryParamError::HashVerificationFailed)?;
		Ok(request)
	}
}