use super::{
error::*, BuildContext, ClientSecurity, CloudCerts, FromParamStr, InstanceName, ParamSource,
TlsSecurity,
};
use crate::host::HostType;
use std::{borrow::Cow, fmt::Debug, num::NonZeroU16, path::PathBuf, time::Duration};
define_env!(
type Error = ParseError;
#[env(GEL_HOST, EDGEDB_HOST)]
host: HostType,
#[env(GEL_PORT, EDGEDB_PORT)]
#[preprocess=ignore_docker_tcp_port]
port: NonZeroU16,
#[env(GEL_DATABASE, EDGEDB_DATABASE)]
database: String,
#[env(GEL_BRANCH, EDGEDB_BRANCH)]
branch: String,
#[env(GEL_USER, EDGEDB_USER)]
user: String,
#[env(GEL_PASSWORD, EDGEDB_PASSWORD)]
password: String,
#[env(GEL_TLS_SERVER_NAME, EDGEDB_TLS_SERVER_NAME)]
tls_server_name: String,
#[env(GEL_CREDENTIALS_FILE, EDGEDB_CREDENTIALS_FILE)]
credentials_file: PathBuf,
#[env(GEL_INSTANCE, EDGEDB_INSTANCE)]
instance: InstanceName,
#[env(GEL_DSN, EDGEDB_DSN)]
dsn: String,
#[env(GEL_SECRET_KEY, EDGEDB_SECRET_KEY)]
secret_key: String,
#[env(GEL_CLIENT_SECURITY, EDGEDB_CLIENT_SECURITY)]
client_security: ClientSecurity,
#[env(GEL_CLIENT_TLS_SECURITY, EDGEDB_CLIENT_TLS_SECURITY)]
client_tls_security: TlsSecurity,
#[env(GEL_TLS_CA, EDGEDB_TLS_CA)]
tls_ca: String,
#[env(GEL_TLS_CA_FILE, EDGEDB_TLS_CA_FILE)]
tls_ca_file: PathBuf,
#[env(GEL_CLOUD_PROFILE, EDGEDB_CLOUD_PROFILE)]
cloud_profile: String,
#[env(_GEL_CLOUD_CERTS, _EDGEDB_CLOUD_CERTS)]
_cloud_certs: CloudCerts,
#[env(GEL_WAIT_UNTIL_AVAILABLE, EDGEDB_WAIT_UNTIL_AVAILABLE)]
wait_until_available: Duration,
);
fn ignore_docker_tcp_port(
s: &str,
context: &impl BuildContext,
) -> Result<Option<String>, ParseError> {
if s.starts_with("tcp://") {
context.warn(Warning::DockerPortIgnored(
"GEL_PORT/EDGEDB_PORT".to_string(),
));
Ok(None)
} else {
Ok(Some(s.to_string()))
}
}
pub use crate::__UNEXPORTED_define_env as define_env;
#[doc(hidden)]
#[macro_export]
macro_rules! __UNEXPORTED_define_env {
(
type Error = $error:ty;
$(
#[doc=$doc:expr]
#[env($($env_name:expr),+)]
$(#[preprocess=$preprocess:expr])?
$(#[parse=$parse:expr])?
$(#[validate=$validate:expr])?
$name:ident: $type:ty
),* $(,)?
) => {
#[derive(Debug, Clone)]
pub struct Env {
}
#[allow(clippy::diverging_sub_expression)]
impl Env {
$(
#[doc = $doc]
pub fn $name(context: &impl $crate::gel::BuildContext) -> ::std::result::Result<::std::option::Option<$type>, $error> {
const ENV_NAMES: &[&str] = &[$(stringify!($env_name)),+];
let Some((_name, s)) = $crate::gel::env::get_envs(ENV_NAMES, context)? else {
return Ok(None);
};
$(let Some(s) = $preprocess(&s, context)? else {
return Ok(None);
};)?
#[allow(unused_labels)]
let value: $type = 'block: {
$(
break 'block $parse(&name, &s)?;
#[cfg(all(debug_assertions, not(debug_assertions)))]
)?
$crate::gel::env::parse::<_, $error>(s, context)?
};
$($validate(name, &value)?;)?
Ok(Some(value))
}
)*
}
};
}
#[inline(never)]
#[doc(hidden)]
pub fn parse<T: FromParamStr, E>(s: impl AsRef<str>, context: &impl BuildContext) -> Result<T, E>
where
<T as FromParamStr>::Err: Into<E>,
{
match T::from_param_str(s.as_ref(), context) {
Ok(value) => Ok(value),
Err(e) => Err(e.into()),
}
}
#[inline(never)]
#[doc(hidden)]
pub fn get_envs(
names: &'static [&'static str],
context: &impl BuildContext,
) -> Result<Option<(&'static str, Cow<'static, str>)>, ParseError> {
let mut value = None;
let mut found_vars = Vec::new();
for name in names {
match context.read_env(name) {
Ok(val) => {
found_vars.push(format!("{name}={val}"));
if value.is_none() {
value = Some((*name, Cow::Owned(val.to_string())));
}
}
Err(std::env::VarError::NotPresent) => continue,
Err(err @ std::env::VarError::NotUnicode(_)) => {
return Err(ParseError::EnvNotFound(
ParamSource::Explicit,
err.to_string(),
));
}
}
}
if found_vars.len() > 1 {
context.warn(Warning::MultipleEnvironmentVariables(found_vars));
}
Ok(value)
}
#[cfg(test)]
mod tests {
use crate::gel::{error::Warning, Warnings};
use std::collections::HashMap;
use super::*;
use crate::gel::BuildContextImpl;
define_env! {
type Error = ParseError;
#[doc="The host to connect to."]
#[env(GEL_HOST, EDGEDB_HOST)]
host: String,
}
#[test]
fn test_define_env() {
let map = HashMap::from([("GEL_HOST", "localhost"), ("EDGEDB_HOST", "localhost")]);
let mut context = BuildContextImpl::new_with(&map, ());
let warnings = Warnings::default();
context.logging.warning = Some(warnings.clone().warn_fn());
assert_eq!(Env::host(&context).unwrap(), Some("localhost".to_string()));
assert_eq!(
warnings.into_vec(),
vec![Warning::MultipleEnvironmentVariables(vec![
"GEL_HOST=localhost".to_string(),
"EDGEDB_HOST=localhost".to_string(),
])]
);
}
}