use std::env as std_env;
use vrl::compiler::prelude::{
kind, ArgumentList, Compiled, Context, Example, Expression, ExpressionError, Function,
FunctionCompileContext, FunctionExpression, Parameter, Resolved, TypeDef, TypeState, Value,
};
#[derive(Clone, Copy, Debug)]
pub struct Env;
impl Function for Env {
fn identifier(&self) -> &'static str {
"env"
}
fn usage(&self) -> &'static str {
"env(name, default)"
}
fn category(&self) -> &'static str {
"os"
}
fn return_kind(&self) -> u16 {
kind::BYTES | kind::NULL
}
fn parameters(&self) -> &'static [Parameter] {
&[
Parameter {
keyword: "name",
kind: kind::BYTES, required: true,
default: None,
description: "The name of the environment variable",
enum_variants: None,
},
Parameter {
keyword: "default",
kind: kind::BYTES,
required: false,
default: None,
description: "A fallback value if the environment variable is not set",
enum_variants: None,
},
Parameter {
keyword: "treat_empty_as_unset",
kind: kind::BOOLEAN,
required: false,
default: None,
description: "If `true`, treats an empty environment variable as unset.",
enum_variants: None,
},
]
}
fn examples(&self) -> &'static [Example] {
&[
Example {
title: "Get an environment variable",
source: r#"env("OTEL_EXPORTER_OTLP_ENDPOINT")"#,
result: Ok("http://collector:4317"),
file: "get_env.vrl",
line: 1,
deterministic: true,
input: None,
skip: false,
},
Example {
title: "Default when unset",
source: r#"env("MISSING_VAR", "fallback")"#,
file: "default_env.vrl",
line: 1,
result: Ok("fallback"),
deterministic: true,
input: None,
skip: false,
},
Example {
title: "Default when unset or empty",
source: r#"env("MAYBE_EMPTY", "fallback", true)"#,
file: "default_empty_env.vrl",
line: 1,
result: Ok("fallback"),
deterministic: true,
input: None,
skip: false,
},
]
}
fn compile(
&self,
_state: &TypeState,
_ctx: &mut FunctionCompileContext,
arguments: ArgumentList,
) -> Compiled {
let name = arguments.required("name");
let default = arguments.optional("default");
let treat_empty_as_unset = arguments.optional("treat_empty_as_unset");
Ok(EnvFn {
name,
default,
treat_empty_as_unset,
}
.as_expr())
}
}
#[derive(Debug, Clone)]
struct EnvFn {
name: Box<dyn Expression>,
default: Option<Box<dyn Expression>>,
treat_empty_as_unset: Option<Box<dyn Expression>>,
}
fn resolve_env(
env_opt: Option<String>,
default_val: Option<Value>,
treat_empty_as_unset: bool,
) -> Value {
if let Some(v) = env_opt {
if !v.is_empty() || !treat_empty_as_unset {
return Value::from(v);
}
}
default_val.unwrap_or(Value::Null)
}
impl FunctionExpression for EnvFn {
fn resolve(&self, ctx: &mut Context) -> Resolved {
let name_val = self.name.resolve(ctx)?;
let name_str = name_val.as_str().ok_or_else(|| ExpressionError::Error {
message: "env(:name) parameter must be a string".to_string(),
labels: vec![],
notes: vec![],
})?;
let default_val = self
.default
.as_ref()
.map(|expr| expr.resolve(ctx))
.transpose()?;
let treat_empty_as_unset = self
.treat_empty_as_unset
.as_ref()
.map(|expr| {
expr.resolve(ctx)?
.as_boolean()
.ok_or_else(|| ExpressionError::Error {
message: "env(?, ?, :treat_empty_as_unset) parameter must be a boolean"
.to_string(),
labels: vec![],
notes: vec![],
})
})
.transpose()?
.unwrap_or(false);
let env_opt = std_env::var(name_str.as_ref()).ok();
Ok(resolve_env(env_opt, default_val, treat_empty_as_unset))
}
fn type_def(&self, _: &TypeState) -> TypeDef {
TypeDef::bytes().add_null().infallible()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_non_empty_false_var_set_with_value() {
let result = resolve_env(Some("value".to_string()), None, false);
assert_eq!(result, Value::from("value"));
}
#[test]
fn test_non_empty_false_var_set_empty() {
let default = Some(Value::from("default"));
let result = resolve_env(Some("".to_string()), default.clone(), false);
assert_eq!(result, Value::from(""));
}
#[test]
fn test_non_empty_false_var_unset_no_default() {
let result = resolve_env(None, None, false);
assert_eq!(result, Value::Null);
}
#[test]
fn test_non_empty_false_var_unset_with_default() {
let default = Some(Value::from("default"));
let result = resolve_env(None, default.clone(), false);
assert_eq!(result, default.unwrap());
}
#[test]
fn test_non_empty_true_var_set_with_value() {
let result = resolve_env(Some("value".to_string()), None, true);
assert_eq!(result, Value::from("value"));
}
#[test]
fn test_non_empty_true_var_set_empty_with_default() {
let default = Some(Value::from("default"));
let result = resolve_env(Some("".to_string()), default.clone(), true);
assert_eq!(result, default.unwrap());
}
#[test]
fn test_non_empty_true_var_set_empty_no_default() {
let result = resolve_env(Some("".to_string()), None, true);
assert_eq!(result, Value::Null);
}
#[test]
fn test_non_empty_true_var_unset_no_default() {
let result = resolve_env(None, None, true);
assert_eq!(result, Value::Null);
}
#[test]
fn test_non_empty_true_var_unset_with_default() {
let default = Some(Value::from("default"));
let result = resolve_env(None, default.clone(), true);
assert_eq!(result, default.unwrap());
}
}