refactor: config: use newtype

This commit is contained in:
Paul Campbell 2024-06-19 07:02:18 +01:00
parent 5e9f9eb80f
commit ea20afee12
18 changed files with 105 additions and 123 deletions

View file

@ -1,2 +1,2 @@
// The name of a Branch // The name of a Branch
crate::newtype!(BranchName is a String); crate::newtype!(BranchName is a String, derive_more::Display, Default);

View file

@ -1,5 +1,5 @@
// The name of a Forge to connect to // The name of a Forge to connect to
crate::newtype!(ForgeAlias is a String); crate::newtype!(ForgeAlias is a String, derive_more::Display, Default);
impl From<&ForgeAlias> for std::path::PathBuf { impl From<&ForgeAlias> for std::path::PathBuf {
fn from(value: &ForgeAlias) -> Self { fn from(value: &ForgeAlias) -> Self {
Self::from(&value.0) Self::from(&value.0)

View file

@ -1,7 +1,7 @@
// //
use std::path::PathBuf; use std::path::PathBuf;
crate::newtype!(GitDir is a PathBuf, without Display); crate::newtype!(GitDir is a PathBuf, Default);
impl GitDir { impl GitDir {
pub const fn pathbuf(&self) -> &PathBuf { pub const fn pathbuf(&self) -> &PathBuf {
&self.0 &self.0
@ -22,8 +22,3 @@ impl From<&GitDir> for PathBuf {
value.to_path_buf() value.to_path_buf()
} }
} }
impl From<GitDir> for PathBuf {
fn from(value: GitDir) -> Self {
value.0
}
}

View file

@ -1,2 +1,2 @@
// The hostname of a forge // The hostname of a forge
crate::newtype!(Hostname is a String); crate::newtype!(Hostname is a String, derive_more::Display, Default);

View file

@ -3,9 +3,9 @@
mod api_token; mod api_token;
mod branch_name; mod branch_name;
pub mod common; pub mod common;
mod forge_alias;
mod forge_config; mod forge_config;
mod forge_details; mod forge_details;
mod forge_name;
mod forge_type; mod forge_type;
pub mod git_dir; pub mod git_dir;
mod host_name; mod host_name;
@ -26,9 +26,9 @@ mod tests;
pub use api_token::ApiToken; pub use api_token::ApiToken;
pub use branch_name::BranchName; pub use branch_name::BranchName;
pub use forge_alias::ForgeAlias;
pub use forge_config::ForgeConfig; pub use forge_config::ForgeConfig;
pub use forge_details::ForgeDetails; pub use forge_details::ForgeDetails;
pub use forge_name::ForgeAlias;
pub use forge_type::ForgeType; pub use forge_type::ForgeType;
pub use git_dir::GitDir; pub use git_dir::GitDir;
pub use host_name::Hostname; pub use host_name::Hostname;

View file

@ -1,35 +1,10 @@
// //
#[macro_export] #[macro_export]
macro_rules! newtype { macro_rules! newtype {
($name:ident is a $type:ty, without Display) => { ($name:ident) => {
#[derive(
Clone,
Default,
Debug,
derive_more::From,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
derive_more::AsRef,
derive_more::Deref,
serde::Deserialize,
serde::Serialize,
)]
pub struct $name($type);
impl $name {
pub fn new(value: impl Into<$type>) -> Self {
Self(value.into())
}
pub fn unwrap(self) -> $type {
self.0
}
}
};
($name:ident is a $type:ty) => {
#[derive( #[derive(
Clone, Clone,
Copy,
Default, Default,
Debug, Debug,
derive_more::Display, derive_more::Display,
@ -40,18 +15,42 @@ macro_rules! newtype {
Ord, Ord,
Hash, Hash,
derive_more::AsRef, derive_more::AsRef,
)]
pub struct $name;
impl $name {
pub fn new() -> Self {
Self
}
}
};
($name:ident is a $type:ty $(, $derive:ty)*) => {
#[derive(
Clone,
Debug,
derive_more::From,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
derive_more::AsRef,
derive_more::Deref, derive_more::Deref,
serde::Deserialize, $($derive),*
serde::Serialize,
)] )]
pub struct $name($type); pub struct $name($type);
impl $name { impl $name {
pub fn new(value: impl Into<$type>) -> Self { pub fn new(value: impl Into<$type>) -> Self {
Self(value.into()) Self(value.into())
} }
#[allow(clippy::missing_const_for_fn)]
pub fn unwrap(self) -> $type { pub fn unwrap(self) -> $type {
self.0 self.0
} }
} }
impl From<$name> for $type {
fn from(value: $name) -> $type {
value.unwrap()
}
}
}; };
} }

View file

@ -2,7 +2,17 @@ use crate::BranchName;
/// Mapped from `.git-next.toml` file at `branches` /// Mapped from `.git-next.toml` file at `branches`
#[derive( #[derive(
Clone, Debug, PartialEq, Eq, serde::Deserialize, derive_more::Constructor, derive_more::Display, Clone,
Hash,
Debug,
PartialEq,
Eq,
PartialOrd,
Ord,
serde::Deserialize,
serde::Serialize,
derive_more::Constructor,
derive_more::Display,
)] )]
#[display("{},{},{}", main, next, dev)] #[display("{},{},{}", main, next, dev)]
pub struct RepoBranches { pub struct RepoBranches {

View file

@ -5,7 +5,17 @@ use crate::RepoConfigSource;
/// Is also derived from the optional parameters in `git-next-server.toml` at /// Is also derived from the optional parameters in `git-next-server.toml` at
/// `forge.{forge}.repos.{repo}.(main|next|dev)` /// `forge.{forge}.repos.{repo}.(main|next|dev)`
#[derive( #[derive(
Clone, Debug, PartialEq, Eq, serde::Deserialize, derive_more::Constructor, derive_more::Display, Clone,
Hash,
Debug,
PartialEq,
Eq,
PartialOrd,
Ord,
serde::Deserialize,
serde::Serialize,
derive_more::Constructor,
derive_more::Display,
)] )]
#[display("{}", branches)] #[display("{}", branches)]
pub struct RepoConfig { pub struct RepoConfig {
@ -13,7 +23,7 @@ pub struct RepoConfig {
source: RepoConfigSource, source: RepoConfigSource,
} }
impl RepoConfig { impl RepoConfig {
pub fn load(toml: &str) -> Result<Self, toml::de::Error> { pub fn parse(toml: &str) -> Result<Self, toml::de::Error> {
toml::from_str(format!("source = \"Repo\"\n{}", toml).as_str()) toml::from_str(format!("source = \"Repo\"\n{}", toml).as_str())
} }

View file

@ -1,4 +1,6 @@
#[derive(Copy, Clone, Debug, PartialEq, Eq, serde::Deserialize)] #[derive(
Copy, Hash, Clone, Debug, PartialEq, Eq, serde::Deserialize, PartialOrd, Ord, serde::Serialize,
)]
pub enum RepoConfigSource { pub enum RepoConfigSource {
Repo, Repo,
Server, Server,

View file

@ -1,6 +1,5 @@
// //
use actix::prelude::*; use actix::prelude::*;
use derive_more::Constructor;
use std::{ use std::{
collections::HashMap, collections::HashMap,
@ -12,7 +11,7 @@ use std::{
use kxio::fs::FileSystem; use kxio::fs::FileSystem;
use tracing::info; use tracing::info;
use crate::{ForgeAlias, ForgeConfig, RepoAlias}; use crate::{newtype, ForgeAlias, ForgeConfig, RepoAlias};
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum Error { pub enum Error {
@ -96,18 +95,8 @@ impl Webhook {
} }
} }
/// The URL for the webhook where forges should send their updates // The RL for the webhook where forges should send their updates
#[derive( newtype!(WebhookUrl is a String, serde::Serialize);
Clone,
Debug,
PartialEq,
Eq,
serde::Serialize,
serde::Deserialize,
derive_more::AsRef,
Constructor,
)]
pub struct WebhookUrl(String);
/// The directory to store server data, such as cloned repos /// The directory to store server data, such as cloned repos
#[derive(Clone, Debug, PartialEq, Eq, serde::Deserialize, derive_more::Constructor)] #[derive(Clone, Debug, PartialEq, Eq, serde::Deserialize, derive_more::Constructor)]

View file

@ -1,18 +1,22 @@
// //
use super::*;
use assert2::let_assert;
use secrecy::ExposeSecret;
use std::collections::BTreeMap;
use std::path::PathBuf;
use crate::server::Http;
use crate::server::ServerConfig;
use crate::server::ServerStorage;
use crate::server::Webhook;
use crate::webhook::push::Branch;
type Result<T> = core::result::Result<T, Box<dyn std::error::Error>>; type Result<T> = core::result::Result<T, Box<dyn std::error::Error>>;
type TestResult = Result<()>; type TestResult = Result<()>;
mod server_repo_config { mod server_repo_config {
use std::path::PathBuf; use super::*;
use assert2::let_assert;
use crate::{
tests::given, BranchName, GitDir, RepoBranches, RepoConfig, RepoConfigSource, RepoPath,
};
use super::super::server_repo_config::*;
#[test] #[test]
fn should_not_return_repo_config_when_no_branches() { fn should_not_return_repo_config_when_no_branches() {
@ -91,9 +95,6 @@ mod server_repo_config {
} }
} }
mod repo_config { mod repo_config {
use crate::{RepoBranches, RepoConfigSource};
use super::super::repo_config::*;
use super::*; use super::*;
#[test] #[test]
@ -110,7 +111,7 @@ mod repo_config {
"# "#
); );
let rc = RepoConfig::load(toml.as_str())?; let rc = RepoConfig::parse(toml.as_str())?;
assert_eq!( assert_eq!(
rc, rc,
@ -144,13 +145,7 @@ mod repo_config {
} }
} }
mod forge_config { mod forge_config {
use std::collections::BTreeMap; use super::*;
use secrecy::ExposeSecret;
use crate::{
tests::given, ForgeConfig, ForgeType, Hostname, RepoAlias, ServerRepoConfig, User,
};
#[test] #[test]
fn should_return_repos() { fn should_return_repos() {
@ -246,14 +241,9 @@ mod forge_config {
} }
} }
mod forge_details { mod forge_details {
use std::collections::BTreeMap; use super::*;
use secrecy::ExposeSecret; use secrecy::ExposeSecret;
use crate::{
tests::given, ApiToken, ForgeAlias, ForgeConfig, ForgeDetails, ForgeType, Hostname, User,
};
#[test] #[test]
fn should_return_forge_alias() { fn should_return_forge_alias() {
let forge_type = ForgeType::MockForge; let forge_type = ForgeType::MockForge;
@ -364,10 +354,9 @@ mod forge_details {
} }
} }
mod forge_name { mod forge_name {
use super::*;
use std::path::PathBuf; use std::path::PathBuf;
use crate::{tests::given, ForgeAlias};
#[test] #[test]
fn should_convert_to_pathbuf() { fn should_convert_to_pathbuf() {
let name = given::a_name(); let name = given::a_name();
@ -379,7 +368,7 @@ mod forge_name {
} }
} }
mod forge_type { mod forge_type {
use crate::ForgeType; use super::*;
#[test] #[test]
fn should_display_as_string() { fn should_display_as_string() {
@ -387,10 +376,9 @@ mod forge_type {
} }
} }
mod gitdir { mod gitdir {
use super::*;
use std::path::PathBuf; use std::path::PathBuf;
use crate::{tests::given, GitDir};
#[test] #[test]
fn should_return_pathbuf() { fn should_return_pathbuf() {
let pathbuf = PathBuf::default().join(given::a_name()); let pathbuf = PathBuf::default().join(given::a_name());
@ -439,7 +427,7 @@ mod gitdir {
} }
} }
mod repo_branches { mod repo_branches {
use crate::{tests::given, BranchName, RepoBranches}; use super::*;
#[test] #[test]
fn should_return_main() { fn should_return_main() {
@ -470,15 +458,10 @@ mod repo_branches {
} }
} }
mod server { mod server {
use super::*;
mod load { mod load {
use assert2::let_assert; use super::*;
use pretty_assertions::assert_eq;
use crate::{
server::ServerConfig,
tests::{given, TestResult},
};
#[test] #[test]
fn load_should_parse_server_config() -> TestResult { fn load_should_parse_server_config() -> TestResult {
@ -559,7 +542,7 @@ token = "{forge_token}"
{repos} {repos}
"# "#
); );
eprintln!("{file_contents}"); println!("{file_contents}");
fs.file_write( fs.file_write(
&fs.base().join("git-next-server.toml"), &fs.base().join("git-next-server.toml"),
file_contents.as_str(), file_contents.as_str(),
@ -569,8 +552,7 @@ token = "{forge_token}"
} }
} }
mod registered_webhook { mod registered_webhook {
use super::*;
use crate::{tests::given, RegisteredWebhook, WebhookAuth};
#[test] #[test]
fn should_return_id() { fn should_return_id() {
@ -590,11 +572,11 @@ mod registered_webhook {
} }
} }
mod webhook { mod webhook {
use super::*;
mod message { mod message {
use super::*;
use std::collections::HashMap; use std::collections::HashMap;
use crate::{tests::given, WebhookMessage};
#[test] #[test]
fn should_return_forge_alias() { fn should_return_forge_alias() {
let forge_alias = given::a_forge_alias(); let forge_alias = given::a_forge_alias();
@ -645,7 +627,8 @@ mod webhook {
} }
} }
mod push { mod push {
use crate::{tests::given, webhook::push::Branch, BranchName};
use super::*;
#[test] #[test]
fn should_return_main_branch() { fn should_return_main_branch() {
@ -717,18 +700,14 @@ mod push {
} }
} }
mod given { mod given {
use super::*;
use rand::Rng as _;
use std::{ use std::{
collections::{BTreeMap, HashMap}, collections::{BTreeMap, HashMap},
path::{Path, PathBuf}, path::{Path, PathBuf},
}; };
use rand::Rng as _;
use crate::{
server::{Http, ServerConfig, ServerStorage, Webhook},
ForgeAlias, ForgeConfig, ForgeType, RepoAlias, RepoBranches, ServerRepoConfig, WebhookId,
};
pub fn a_name() -> String { pub fn a_name() -> String {
use rand::Rng; use rand::Rng;
use std::iter; use std::iter;

View file

@ -1,7 +1,7 @@
#[derive(Clone, Debug, PartialEq, Eq, derive_more::Deref, derive_more::Display)] //
pub struct WebhookAuth(ulid::Ulid); crate::newtype!(WebhookAuth is a ulid::Ulid, derive_more::Display);
impl WebhookAuth { impl WebhookAuth {
pub fn new(authorisation: &str) -> Result<Self, ulid::DecodeError> { pub fn try_new(authorisation: &str) -> Result<Self, ulid::DecodeError> {
use std::str::FromStr as _; use std::str::FromStr as _;
let id = ulid::Ulid::from_str(authorisation)?; let id = ulid::Ulid::from_str(authorisation)?;
tracing::info!("Parse auth token: {}", id); tracing::info!("Parse auth token: {}", id);

View file

@ -1,4 +1,2 @@
use derive_more::{Constructor, Deref, Display}; //
crate::newtype!(WebhookId is a String, derive_more::Display);
#[derive(Clone, Debug, PartialEq, Eq, Constructor, Deref, Display)]
pub struct WebhookId(String);

View file

@ -5,7 +5,7 @@ mod auth {
fn bytes() -> Result<(), Box<dyn std::error::Error>> { fn bytes() -> Result<(), Box<dyn std::error::Error>> {
let ulid = ulid::Ulid::new(); let ulid = ulid::Ulid::new();
let wa = WebhookAuth::new(ulid.to_string().as_str())?; let wa = WebhookAuth::try_new(ulid.to_string().as_str())?;
assert_eq!(ulid.to_bytes(), wa.to_bytes()); assert_eq!(ulid.to_bytes(), wa.to_bytes());
@ -16,7 +16,7 @@ mod auth {
fn string() -> Result<(), Box<dyn std::error::Error>> { fn string() -> Result<(), Box<dyn std::error::Error>> {
let ulid = ulid::Ulid::new(); let ulid = ulid::Ulid::new();
let wa = WebhookAuth::new(ulid.to_string().as_str())?; let wa = WebhookAuth::try_new(ulid.to_string().as_str())?;
assert_eq!(ulid.to_string(), wa.to_string()); assert_eq!(ulid.to_string(), wa.to_string());

View file

@ -36,7 +36,7 @@ impl git::ForgeLike for ForgeJo {
tracing::info!(?authorization, %expected, "is message authorised?"); tracing::info!(?authorization, %expected, "is message authorised?");
authorization authorization
.and_then(|header| header.strip_prefix("Basic ").map(|v| v.to_owned())) .and_then(|header| header.strip_prefix("Basic ").map(|v| v.to_owned()))
.and_then(|value| config::WebhookAuth::new(value.as_str()).ok()) .and_then(|value| config::WebhookAuth::try_new(value.as_str()).ok())
.map(|auth| &auth == expected) .map(|auth| &auth == expected)
.unwrap_or(false) .unwrap_or(false)
} }

View file

@ -114,7 +114,7 @@ mod repo_config {
"# "#
); );
let rc = config::RepoConfig::load(toml.as_str())?; let rc = config::RepoConfig::parse(toml.as_str())?;
assert_eq!( assert_eq!(
rc, rc,

View file

@ -34,7 +34,7 @@ async fn load(
open_repository: &git::OpenRepository, open_repository: &git::OpenRepository,
) -> Result<config::RepoConfig, Error> { ) -> Result<config::RepoConfig, Error> {
let contents = open_repository.read_file(&details.branch, &PathBuf::from(".git-next.toml"))?; let contents = open_repository.read_file(&details.branch, &PathBuf::from(".git-next.toml"))?;
let config = config::RepoConfig::load(&contents)?; let config = config::RepoConfig::parse(&contents)?;
let config = validate(config, open_repository).await?; let config = validate(config, open_repository).await?;
Ok(config) Ok(config)
} }

View file

@ -18,7 +18,7 @@ fn test_repo_config_load() -> Result<()> {
[options] [options]
"#; "#;
let config = RepoConfig::load(toml)?; let config = RepoConfig::parse(toml)?;
assert_eq!( assert_eq!(
config, config,