Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simplify the code for getting environment variables #99

Merged
merged 1 commit into from
Feb 24, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
273 changes: 140 additions & 133 deletions fediproto-sync-lib/src/config.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
use openssl::{
pkey::{Private, Public},
rsa::Rsa,
};

use crate::{GIT_VERSION, error::FediProtoSyncError};

static FEDIPROTO_SYNC_MODE_ENV_VAR: &str = "FEDIPROTO_SYNC_MODE";
Expand All @@ -19,6 +24,64 @@ static SYNC_INTERVAL_SECONDS_ENV_VAR: &str = "SYNC_INTERVAL_SECONDS";
static BLUESKY_VIDEO_ALWAYS_FALLBACK_ENV_VAR: &str = "BLUESKY_VIDEO_ALWAYS_FALLBACK";
static MASTODON_ALLOW_UNLISTED_POSTS_ENV_VAR: &str = "MASTODON_ALLOW_UNLISTED_POSTS";

/// Macro for getting an environment variable value.
///
/// ## Arguments
///
/// ### Regular
///
/// Get the value of an environment variable. If it doesn't exist, `FediProtoSyncError` is the result.
///
/// * `name` - The environment variable key name to get.
///
/// ### With fallback
///
/// Get the value of an environment variable. If it doesn't exist, the fallback value is returned.
///
/// * `name` - The environment variable key name to get.
/// * `fallback_value` - The fallback value to return if the environment variable doesn't exist.
///
/// ### Regular, with parsing
///
/// Get the value of an environment variable and parse it to the specified type.
/// If it doesn't exist, `FediProtoSyncError` is the result.
///
/// * `name` - The environment variable key name to get.
/// * `parse_to` - The type to parse the value to.
///
/// ### With fallback and parsing
///
/// Get the value of an environment variable and parse it to the specified type.
/// If it doesn't exist, the fallback value is returned.
///
/// * `name` - The environment variable key name to get.
/// * `fallback_value` - The fallback value to return if the environment variable doesn't exist.
/// * `parse_to` - The type to parse the value to.
macro_rules! get_env_var {
($name:expr) => {
std::env::var($name)
.map_err(|_| FediProtoSyncError::EnvironmentVariableError($name.to_string()))
};

($name:expr, $fallback_value:expr) => {
std::env::var($name).unwrap_or_else(|_| $fallback_value.to_string())
};

($name:expr, $parse_to:ty) => {
std::env::var($name)
.map_err(|_| FediProtoSyncError::EnvironmentVariableError($name.to_string()))?
.parse::<$parse_to>()
.map_err(|_| FediProtoSyncError::EnvironmentVariableParseError($name.to_string()))
};

($name:expr, $fallback_value:expr, $parse_to:ty) => {
std::env::var($name)
.unwrap_or_else(|_| $fallback_value.to_string())
.parse::<$parse_to>()
.map_err(|_| FediProtoSyncError::EnvironmentVariableParseError($name.to_string()))
};
}

/// Config values for configuring the FediProtoSync
/// application.
#[derive(Debug, Clone)]
Expand Down Expand Up @@ -109,181 +172,85 @@ pub struct FediProtoSyncConfig {
pub bluesky_video_always_fallback: bool,

/// Whether to allow unlisted posts from Mastodon to sync to BlueSky.
///
///
/// **Environment variable:** `MASTODON_ALLOW_UNLISTED_POSTS`
pub mastodon_allow_unlisted_posts: bool
pub mastodon_allow_unlisted_posts: bool,
}

impl FediProtoSyncConfig {
/// Create a new instance of the `FediProtoSyncConfig` struct.
pub fn new() -> Result<Self, FediProtoSyncError> {
// Read 'FEDIPROTO_SYNC_MODE' environment variable.
let mode = std::env::var(FEDIPROTO_SYNC_MODE_ENV_VAR)
.unwrap_or("normal".to_string())
.parse::<FediProtoSyncMode>()
.map_err(|_| {
FediProtoSyncError::EnvironmentVariableError(
FEDIPROTO_SYNC_MODE_ENV_VAR.to_string()
)
})?;
let mode = get_env_var!(FEDIPROTO_SYNC_MODE_ENV_VAR, "normal", FediProtoSyncMode)?;

// Read 'AUTH_SERVER_ADDRESS' environment variable.
let auth_server_address = match mode {
FediProtoSyncMode::Auth => Some(
std::env::var(AUTH_SERVER_ADDRESS_ENV_VAR)
.unwrap_or_else(|_| "localhost".to_string())
),
FediProtoSyncMode::Auth => Some(get_env_var!(AUTH_SERVER_ADDRESS_ENV_VAR, "localhost")),

FediProtoSyncMode::Normal => None
FediProtoSyncMode::Normal => None,
};

// Read 'AUTH_SERVER_PORT' environment variable.
let auth_server_port = match mode {
FediProtoSyncMode::Auth => Some(
std::env::var(AUTH_SERVER_PORT_ENV_VAR)
.unwrap_or_else(|_| "3000".to_string())
.parse::<u16>()
.map_err(|_| {
FediProtoSyncError::EnvironmentVariableParseError(
AUTH_SERVER_PORT_ENV_VAR.to_string()
)
})?
),

FediProtoSyncMode::Normal => None
FediProtoSyncMode::Auth => Some(get_env_var!(AUTH_SERVER_PORT_ENV_VAR, "3000", u16)?),

FediProtoSyncMode::Normal => None,
};

// Read 'DATABASE_TYPE' environment variable.
let database_type = std::env::var(DATABASE_TYPE_ENV_VAR)
.unwrap_or("Postgres".to_string())
.parse::<DatabaseType>()?;
let database_type = get_env_var!(DATABASE_TYPE_ENV_VAR, "postgres", DatabaseType)?;

// Read 'DATABASE_URL' environment variable.
let database_url = std::env::var(DATABASE_URL_ENV_VAR).map_err(|_| {
FediProtoSyncError::EnvironmentVariableError(DATABASE_URL_ENV_VAR.to_string())
})?;
let database_url = get_env_var!(DATABASE_URL_ENV_VAR)?;

// Read 'TOKEN_ENCRYPTION_PRIVATE_KEY' environment variable.
let token_encryption_private_key = std::env::var(TOKEN_ENCRYPTION_PRIVATE_KEY_ENV_VAR)
.map_err(|_| {
FediProtoSyncError::EnvironmentVariableError(
TOKEN_ENCRYPTION_PRIVATE_KEY_ENV_VAR.to_string()
)
})?;

let token_encryption_private_key =
openssl::base64::decode_block(&token_encryption_private_key).map_err(|_| {
FediProtoSyncError::EnvironmentVariableError(
TOKEN_ENCRYPTION_PRIVATE_KEY_ENV_VAR.to_string()
)
})?;

let token_encryption_private_key = openssl::rsa::Rsa::private_key_from_pem(
&token_encryption_private_key
)
.map_err(|_| {
FediProtoSyncError::EnvironmentVariableError(
TOKEN_ENCRYPTION_PRIVATE_KEY_ENV_VAR.to_string()
)
})?;
get_token_encryption_private_key_from_environment_variable()?;

// Read 'TOKEN_ENCRYPTION_PUBLIC_KEY' environment variable.
let token_encryption_public_key = std::env::var(TOKEN_ENCRYPTION_PUBLIC_KEY_ENV_VAR)
.map_err(|_| {
FediProtoSyncError::EnvironmentVariableError(
TOKEN_ENCRYPTION_PUBLIC_KEY_ENV_VAR.to_string()
)
})?;

let token_encryption_public_key =
openssl::base64::decode_block(&token_encryption_public_key).map_err(|_| {
FediProtoSyncError::EnvironmentVariableError(
TOKEN_ENCRYPTION_PUBLIC_KEY_ENV_VAR.to_string()
)
})?;

let token_encryption_public_key =
openssl::rsa::Rsa::public_key_from_pem(&token_encryption_public_key).map_err(|_| {
FediProtoSyncError::EnvironmentVariableError(
TOKEN_ENCRYPTION_PUBLIC_KEY_ENV_VAR.to_string()
)
})?;
get_token_encryption_public_key_from_environment_variable()?;

// Read 'USER_AGENT' environment variable.
let user_agent =
std::env::var(USER_AGENT_ENV_VAR).unwrap_or_else(|_| "FediProto Sync".to_string());
let user_agent = get_env_var!(USER_AGENT_ENV_VAR, "FediProto Sync");

let user_agent = format!("{}/v{}", user_agent, GIT_VERSION);

// Read 'MASTODON_SERVER' environment variable.
let mastodon_server = std::env::var(MASTODON_SERVER_ENV_VAR).map_err(|_| {
FediProtoSyncError::EnvironmentVariableError(MASTODON_SERVER_ENV_VAR.to_string())
})?;
let mastodon_server = get_env_var!(MASTODON_SERVER_ENV_VAR)?;

// Read 'MASTODON_CLIENT_ID' environment variable.
let mastodon_client_id = std::env::var(MASTODON_CLIENT_ID_ENV_VAR).map_err(|_| {
FediProtoSyncError::EnvironmentVariableError(MASTODON_CLIENT_ID_ENV_VAR.to_string())
})?;
let mastodon_client_id = get_env_var!(MASTODON_CLIENT_ID_ENV_VAR)?;

// Read 'MASTODON_CLIENT_SECRET' environment variable.
let mastodon_client_secret =
std::env::var(MASTODON_CLIENT_SECRET_ENV_VAR).map_err(|_| {
FediProtoSyncError::EnvironmentVariableError(
MASTODON_CLIENT_SECRET_ENV_VAR.to_string()
)
})?;
let mastodon_client_secret = get_env_var!(MASTODON_CLIENT_SECRET_ENV_VAR)?;

// Read 'MASTODON_REDIRECT_URI' environment variable.
let mastodon_redirect_uri = std::env::var(MASTODON_REDIRECT_URI_ENV_VAR).map_err(|_| {
FediProtoSyncError::EnvironmentVariableError(MASTODON_REDIRECT_URI_ENV_VAR.to_string())
})?;
let mastodon_redirect_uri = get_env_var!(MASTODON_REDIRECT_URI_ENV_VAR)?;

// Read 'BLUESKY_PDS_SERVER' environment variable.
let bluesky_pds_server = std::env::var(BLUESKY_PDS_SERVER_ENV_VAR).map_err(|_| {
FediProtoSyncError::EnvironmentVariableError(BLUESKY_PDS_SERVER_ENV_VAR.to_string())
})?;
let bluesky_pds_server = get_env_var!(BLUESKY_PDS_SERVER_ENV_VAR)?;

// Read 'BLUESKY_HANDLE' environment variable.
let bluesky_handle = std::env::var(BLUESKY_HANDLE_ENV_VAR).map_err(|_| {
FediProtoSyncError::EnvironmentVariableError(BLUESKY_HANDLE_ENV_VAR.to_string())
})?;
let bluesky_handle = get_env_var!(BLUESKY_HANDLE_ENV_VAR)?;

// Read 'BLUESKY_APP_PASSWORD' environment variable.
let bluesky_app_password = std::env::var(BLUESKY_APP_PASSWORD_ENV_VAR).map_err(|_| {
FediProtoSyncError::EnvironmentVariableError(BLUESKY_APP_PASSWORD_ENV_VAR.to_string())
})?;
let bluesky_app_password = get_env_var!(BLUESKY_APP_PASSWORD_ENV_VAR)?;

// Read 'SYNC_INTERVAL_SECONDS' environment variable.
let sync_interval = std::time::Duration::from_secs(
std::env::var(SYNC_INTERVAL_SECONDS_ENV_VAR)
.unwrap_or("300".to_string())
.parse::<u64>()
.map_err(|_| {
FediProtoSyncError::EnvironmentVariableParseError(
SYNC_INTERVAL_SECONDS_ENV_VAR.to_string()
)
})?
);
let sync_interval = std::time::Duration::from_secs(get_env_var!(
SYNC_INTERVAL_SECONDS_ENV_VAR,
"300",
u64
)?);

// Read 'BLUESKY_VIDEO_ALWAYS_FALLBACK' environment variable.
let bluesky_video_always_fallback = std::env::var(BLUESKY_VIDEO_ALWAYS_FALLBACK_ENV_VAR)
.unwrap_or("false".to_string())
.parse::<bool>()
.map_err(|_| {
FediProtoSyncError::EnvironmentVariableParseError(
BLUESKY_VIDEO_ALWAYS_FALLBACK_ENV_VAR.to_string()
)
})?;
let bluesky_video_always_fallback =
get_env_var!(BLUESKY_VIDEO_ALWAYS_FALLBACK_ENV_VAR, "false", bool)?;

// Read 'MASTODON_ALLOW_UNLISTED_POSTS' environment variable.
let mastodon_allow_unlisted_posts = std::env::var(MASTODON_ALLOW_UNLISTED_POSTS_ENV_VAR)
.unwrap_or("false".to_string())
.parse::<bool>()
.map_err(|_| {
FediProtoSyncError::EnvironmentVariableParseError(
MASTODON_ALLOW_UNLISTED_POSTS_ENV_VAR.to_string()
)
})?;
let mastodon_allow_unlisted_posts =
get_env_var!(MASTODON_ALLOW_UNLISTED_POSTS_ENV_VAR, "false", bool)?;

Ok(Self {
mode,
Expand All @@ -303,23 +270,23 @@ impl FediProtoSyncConfig {
bluesky_app_password,
sync_interval,
bluesky_video_always_fallback,
mastodon_allow_unlisted_posts
mastodon_allow_unlisted_posts,
})
}
}

#[derive(Debug, Clone, PartialEq)]
pub enum FediProtoSyncMode {
Auth,
Normal
Normal,
}

impl From<&str> for FediProtoSyncMode {
fn from(s: &str) -> Self {
match s {
"auth" => FediProtoSyncMode::Auth,
"normal" => FediProtoSyncMode::Normal,
_ => FediProtoSyncMode::Normal
_ => FediProtoSyncMode::Normal,
}
}
}
Expand All @@ -332,8 +299,8 @@ impl std::str::FromStr for FediProtoSyncMode {
"auth" => Ok(FediProtoSyncMode::Auth),
"normal" => Ok(FediProtoSyncMode::Normal),
_ => Err(FediProtoSyncError::EnvironmentVariableParseError(
FEDIPROTO_SYNC_MODE_ENV_VAR.to_string()
))
FEDIPROTO_SYNC_MODE_ENV_VAR.to_string(),
)),
}
}
}
Expand All @@ -345,15 +312,15 @@ pub enum DatabaseType {
Postgres,

/// SQLite database
SQLite
SQLite,
}

impl From<&str> for DatabaseType {
fn from(s: &str) -> Self {
match s {
"Postgres" => DatabaseType::Postgres,
"SQLite" => DatabaseType::SQLite,
_ => DatabaseType::Postgres
_ => DatabaseType::Postgres,
}
}
}
Expand All @@ -365,7 +332,47 @@ impl std::str::FromStr for DatabaseType {
match s {
"Postgres" => Ok(DatabaseType::Postgres),
"SQLite" => Ok(DatabaseType::SQLite),
_ => Err(FediProtoSyncError::InvalidDatabaseType)
_ => Err(FediProtoSyncError::InvalidDatabaseType),
}
}
}

/// Gets the token encryption private key and decodes it from it's environment variable (`TOKEN_ENCRYPTION_PRIVATE_KEY`).
pub fn get_token_encryption_private_key_from_environment_variable()
-> Result<Rsa<Private>, FediProtoSyncError> {
// Read 'TOKEN_ENCRYPTION_PRIVATE_KEY' environment variable.
let token_encryption_private_key = get_env_var!(TOKEN_ENCRYPTION_PRIVATE_KEY_ENV_VAR)?;

let token_encryption_private_key = openssl::base64::decode_block(&token_encryption_private_key)
.map_err(|_| {
FediProtoSyncError::EnvironmentVariableError(
TOKEN_ENCRYPTION_PRIVATE_KEY_ENV_VAR.to_string(),
)
})?;

openssl::rsa::Rsa::private_key_from_pem(&token_encryption_private_key).map_err(|_| {
FediProtoSyncError::EnvironmentVariableError(
TOKEN_ENCRYPTION_PRIVATE_KEY_ENV_VAR.to_string(),
)
})
}

/// Gets the token encryption public key and decodes it from it's environment variable (`TOKEN_ENCRYPTION_PUBLOC_KEY`).
pub fn get_token_encryption_public_key_from_environment_variable()
-> Result<Rsa<Public>, FediProtoSyncError> {
// Read 'TOKEN_ENCRYPTION_PUBLIC_KEY' environment variable.
let token_encryption_public_key = get_env_var!(TOKEN_ENCRYPTION_PUBLIC_KEY_ENV_VAR)?;

let token_encryption_public_key = openssl::base64::decode_block(&token_encryption_public_key)
.map_err(|_| {
FediProtoSyncError::EnvironmentVariableError(
TOKEN_ENCRYPTION_PUBLIC_KEY_ENV_VAR.to_string(),
)
})?;

openssl::rsa::Rsa::public_key_from_pem(&token_encryption_public_key).map_err(|_| {
FediProtoSyncError::EnvironmentVariableError(
TOKEN_ENCRYPTION_PUBLIC_KEY_ENV_VAR.to_string(),
)
})
}