git-gemini-forge 0.6.1

A simple Gemini server that serves a read-only view of public repositories from a Git forge.
use humantime::format_duration;
use std::time::SystemTime;
use windmark::response::Response;

/// The ways that retrieving config values might fail.
#[derive(Debug)]
pub enum Error {
	/// A problem occurred when communicating with the server.
	NetworkFailure,

	/// The requested resource was not found.
	ResourceNotFound,

	/// The upstream's robots.txt file restricts communication at this endpoint.
	Restricted,

	/// The upstream has throttled us, and asked that we wait until after the given time before trying again.
	Throttled(Option<SystemTime>),

	/// The forge rejected our request. A token may be required.
	Unauthorized,

	/// The returned response did not match the expected shape.
	UnexpectedResponse,
}

impl core::fmt::Display for Error {
	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
		// Describe the various kinds of errors:
		match self {
			Self::NetworkFailure => write!(f, "Failed to communicate with the git forge"),
			Self::ResourceNotFound => write!(f, "The requested resource was not found"),
			Self::Restricted => write!(f, "The forge has asked us not to go here"),
			Self::Throttled(None) => {
				writeln!(f, "The forge asked us to wait a while before trying again")
			}
			Self::Throttled(Some(retry_after)) => {
				match retry_after.duration_since(SystemTime::now()) {
					Err(_) => writeln!(f, "The forge asked us to wait a while before trying again"),
					Ok(remaining) => writeln!(
						f,
						"The forge asked us to wait another {} before trying again",
						format_duration(remaining)
					),
				}
			}
			Self::Unauthorized => write!(
				f,
				"We are not authorized to access the upstream; make sure FORGE_SECRET_KEY is set"
			),
			Self::UnexpectedResponse => {
				write!(
					f,
					"Can't understand the response from the upstream; is FORGE_TYPE set correctly?"
				)
			}
		}
	}
}

impl Error {
	/// A Gemini Protocol response suitable for displaying this error to the user.
	pub fn gemini_response(&self) -> Response {
		match self {
			Self::NetworkFailure => Response::proxy_error("Couldn't communicate with the forge"),
			Self::ResourceNotFound => Response::not_found("Not found"),
			Self::Restricted => Response::proxy_refused(format!("{self}")),
			Self::Throttled(_) => Response::temporary_failure(format!("{self}")),
			Self::Unauthorized => Response::proxy_error(
				"We aren't authorized to communicate with the forge on this route",
			),
			Self::UnexpectedResponse => {
				Response::temporary_failure("The upstream gave us a response we don't understand")
			}
		}
	}
}

impl std::error::Error for Error {}