git-gemini-forge 0.6.2

A simple Gemini server that serves a read-only view of public repositories from a Git forge.
use bytesize::ByteSize;
use serde::Deserialize;
use std::cmp::Ordering;
use url::Url;

#[derive(Deserialize, Debug)]
pub struct ServerVersion {
	pub version: String,
}

#[derive(Deserialize, Debug)]
pub struct User {
	pub login: String,
}

#[cfg(test)]
impl User {
	/// Constructs a new [`User`] value.
	pub fn new<S: ToString>(login: S) -> Self {
		User {
			login: login.to_string(),
		}
	}
}

#[derive(Deserialize, Debug)]
pub struct UsersSearch {
	pub data: Vec<User>,
}

/// Describes a code repository.
#[derive(Deserialize, Debug)]
pub struct Repo {
	pub default_branch: String,
	pub html_url: Url,
	pub name: String,
	pub description: String,
	pub website: String,
	pub owner: User,
	pub updated_at: String,

	/// If the repository is a fork of another, these are the details of the parent.
	pub parent: Option<Box<Repo>>,
}

#[derive(Deserialize, Debug)]
pub struct RepoSearch {
	pub data: Vec<Repo>,
}

#[derive(Deserialize, Debug)]
pub struct Branch {
	pub name: String,
}

#[cfg(test)]
impl Branch {
	/// Constructs a new [`Branch`] value.
	pub fn new<S: ToString>(name: S) -> Self {
		Branch {
			name: name.to_string(),
		}
	}
}

/// Describes the way a file's contents are encoded.
#[derive(Deserialize, PartialEq, Eq, Debug)]
#[serde(rename_all = "lowercase")]
pub enum FileEncoding {
	/// Contents are encoded in base 64.
	Base64,
}

/// Describes the contents of a code repository. Either a list of files and folders or the contents of a single file.
#[derive(Deserialize, PartialEq, Eq, Debug)]
#[serde(tag = "type", rename_all = "lowercase")]
pub enum Item {
	File {
		/// The contents of the file, if provided, encoded in the format given in `encoding`.
		/// The absence of only one of these values is an error.
		content: Option<String>,
		encoding: Option<FileEncoding>,
		name: String,
		path: String,
		size: ByteSize,
		html_url: Url,
	},
	Dir {
		name: String,
		path: String,
		size: ByteSize,
		html_url: Url,
	},
	Symlink {
		target: String,
		name: String,
		path: String,
		html_url: Url,
	},
	Submodule {
		submodule_git_url: String,
		name: String,
		path: String,
		html_url: Url,
	},
}

impl Item {
	pub fn name(&self) -> &str {
		match self {
			Self::File { name, .. } => name,
			Self::Dir { name, .. } => name,
			Self::Symlink { name, .. } => name,
			Self::Submodule { name, .. } => name,
		}
	}

	pub fn path(&self) -> &String {
		match self {
			Self::File { path, .. } => path,
			Self::Dir { path, .. } => path,
			Self::Symlink { path, .. } => path,
			Self::Submodule { path, .. } => path,
		}
	}
}

impl Ord for Item {
	fn cmp(&self, other: &Self) -> Ordering {
		// Sort (file/symlink; alphabetically) before (dir/submodule; alphabetically)
		match self {
			Item::Dir { name, .. } | Item::Submodule { name, .. } => {
				let a_name = name;
				match other {
					Item::File { .. } | Item::Symlink { .. } => Ordering::Less,
					Item::Dir { name, .. } | Item::Submodule { name, .. } => {
						let b_name = name;
						a_name.to_lowercase().cmp(&b_name.to_lowercase())
					}
				}
			}
			Item::File { name, .. } | Item::Symlink { name, .. } => {
				let a_name = name;
				match other {
					Item::Dir { .. } | Item::Submodule { .. } => Ordering::Greater,
					Item::File { name, .. } | Item::Symlink { name, .. } => {
						let b_name = name;
						a_name.to_lowercase().cmp(&b_name.to_lowercase())
					}
				}
			}
		}
	}
}

impl PartialOrd for Item {
	fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
		Some(self.cmp(other))
	}
}

/// Describes either an object or a list of that object.
#[derive(Deserialize, Debug)]
#[serde(untagged)]
pub enum Contents<T> {
	Single(T),
	Multiple(Vec<T>),
}