fluffer 4.0.2

🦊 Fluffer is a fun and experimental gemini server framework.
Documentation
use crate::async_trait;
use std::fmt::Display;

/// 💎 A trait implemented on types that can be returned as a gemini response.
#[async_trait]
pub trait GemBytes {
	/// Return this type as a Gemini byte response.
	async fn gem_bytes(self) -> Vec<u8>;
}

#[async_trait]
impl<T, E> GemBytes for Result<T, E>
where
	T: GemBytes + Send,
	E: GemBytes + Send,
{
	async fn gem_bytes(self) -> Vec<u8> {
		match self {
			Ok(o) => o.gem_bytes().await,
			Err(e) => e.gem_bytes().await,
		}
	}
}

/// 💎 Returns some response, or a "None" TemporaryFailure.
#[async_trait]
impl<T> GemBytes for Option<T>
where
	T: GemBytes + Send,
{
	async fn gem_bytes(self) -> Vec<u8> {
		match self {
			Some(s) => s.gem_bytes().await,
			None => "40 None.\r\n".to_string().into_bytes(),
		}
	}
}

/// 💎 `(20, "text/gemini", "# Tuple test :o")`
#[async_trait]
impl<STATUS, META, BODY> GemBytes for (STATUS, META, BODY)
where
	STATUS: Into<u8> + Send,
	META: Display + Send,
	BODY: Display + Send,
{
	async fn gem_bytes(self) -> Vec<u8> {
		format!("{} {}\r\n{}", self.0.into(), self.1, self.2).into_bytes()
	}
}

/// 💎 `(51, "Page couldn't be found")`
#[async_trait]
impl<STATUS, META> GemBytes for (STATUS, META)
where
	STATUS: Into<u8> + Send,
	META: Display + Send,
{
	async fn gem_bytes(self) -> Vec<u8> {
		format!("{} {}\r\n", self.0.into(), self.1).into_bytes()
	}
}

/// 💎 Displays as gemtext.
#[async_trait]
impl GemBytes for &str {
	async fn gem_bytes(self) -> Vec<u8> {
		format!("20 text/gemini\r\n{self}").into_bytes()
	}
}

/// 💎 For raw gemini responses.
#[async_trait]
impl GemBytes for Vec<u8> {
	async fn gem_bytes(self) -> Vec<u8> {
		self
	}
}

/// 💎 Displays as gemtext.
#[async_trait]
impl GemBytes for String {
	async fn gem_bytes(self) -> Vec<u8> {
		format!("20 text/gemini\r\n{}", &self).into_bytes()
	}
}

/// 💎 Displays as gemtext. ¯\_(ツ)_/¯
#[async_trait]
impl GemBytes for u32 {
	async fn gem_bytes(self) -> Vec<u8> {
		format!("20 text/gemini\r\n{self}").into_bytes()
	}
}

/// 💎 Message that the route returns nothing.
#[async_trait]
impl GemBytes for () {
	async fn gem_bytes(self) -> Vec<u8> {
		"40 This route returns nothing yet.\r\n"
			.to_string()
			.into_bytes()
	}
}

/// 💎 Message displaying true or false. Responds with either TemporaryFailure
/// or Success.
#[async_trait]
impl GemBytes for bool {
	async fn gem_bytes(self) -> Vec<u8> {
		if self {
			"20 text/gemini\r\nTrue."
		} else {
			"40 False.\r\n"
		}
		.into()
	}
}

#[cfg(feature = "reqwest")]
/// 💎 For proxying http with [`reqwest`].
///
/// Responds with ProxyError if anything fails.
#[async_trait]
impl GemBytes for reqwest::Result<reqwest::Response> {
	async fn gem_bytes(self) -> Vec<u8> {
		// Catch Result errors
		let response = match self {
			Ok(o) => o,
			Err(e) => return format!("43 http error :: {}\r\n", e.without_url()).into_bytes(),
		};

		response.gem_bytes().await
	}
}

#[cfg(feature = "reqwest")]
/// 💎 For proxying http with [`reqwest`].
///
/// Responds with ProxyError if anything fails.
#[async_trait]
impl GemBytes for reqwest::Response {
	async fn gem_bytes(self) -> Vec<u8> {
		let status = self.status();
		if status != reqwest::StatusCode::OK {
			return format!("43 http: {status}\r\n").into_bytes();
		}

		let content_type = match self.headers().get("Content-Type") {
			Some(o) => o,
			None => {
				return "43 http: invalid content type.\r\n"
					.to_string()
					.into_bytes()
			}
		};
		let content_type = match content_type.to_str() {
			Ok(o) => o,
			Err(_) => {
				return "43 http: content type corrupted.\r\n"
					.to_string()
					.into_bytes()
			}
		};

		let mut output = format!("20 {content_type}\r\n").into_bytes();

		let mut bytes = match self.bytes().await {
			Ok(o) => o,
			Err(e) => return format!("43 http: {}\r\n", e.without_url()).into_bytes(),
		}
		.to_vec();

		output.append(&mut bytes);
		output
	}
}

#[cfg(feature = "anyhow")]
/// 💎 For rapid testing with [`anyhow`].
#[async_trait]
impl GemBytes for anyhow::Error {
	async fn gem_bytes(self) -> Vec<u8> {
		format!("40 {}\r\n", self).into_bytes()
	}
}

/// 💎 Quickly proxy a [`trotter`] response.
#[async_trait]
impl GemBytes for trotter::Response {
	async fn gem_bytes(mut self) -> Vec<u8> {
		let trotter::Response {
			status,
			meta,
			mut content,
			certificate: _,
		} = self;

		let mut o = format!("{status} {meta}\r\n").into_bytes();
		o.append(&mut content);
		o
	}
}