Skip to content

Feat: add FromEnv::inventory() #7

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

Merged
merged 4 commits into from
Apr 24, 2025
Merged
Show file tree
Hide file tree
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
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ name = "init4-bin-base"
description = "Internal utilities for binaries produced by the init4 team"
keywords = ["init4", "bin", "base"]

version = "0.1.4"
version = "0.2.0"
edition = "2021"
rust-version = "1.81"
authors = ["init4", "James Prestwich"]
Expand Down
14 changes: 13 additions & 1 deletion src/perms/builders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use crate::{
perms::{SlotAuthzConfig, SlotAuthzConfigError},
utils::{
calc::SlotCalculator,
from_env::{FromEnv, FromEnvErr, FromEnvVar},
from_env::{EnvItemInfo, FromEnv, FromEnvErr, FromEnvVar},
},
};

Expand Down Expand Up @@ -173,6 +173,18 @@ impl Builders {
impl FromEnv for Builders {
type Error = BuilderConfigError;

fn inventory() -> Vec<&'static EnvItemInfo> {
let mut v = vec![
&EnvItemInfo {
var: BUILDERS,
description: "A comma-separated list of UUIDs representing the builders that are allowed to perform actions.",
optional: false,
},
];
v.extend(SlotAuthzConfig::inventory());
v
}

fn from_env() -> Result<Self, FromEnvErr<Self::Error>> {
let s = String::from_env_var(BUILDERS)
.map_err(FromEnvErr::infallible_into::<BuilderConfigError>)?;
Expand Down
17 changes: 16 additions & 1 deletion src/perms/config.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::utils::{
calc::{SlotCalcEnvError, SlotCalculator},
from_env::{FromEnv, FromEnvErr, FromEnvVar},
from_env::{EnvItemInfo, FromEnv, FromEnvErr, FromEnvVar},
};
use core::num;

Expand Down Expand Up @@ -76,6 +76,21 @@ impl SlotAuthzConfig {
impl FromEnv for SlotAuthzConfig {
type Error = SlotAuthzConfigError;

fn inventory() -> Vec<&'static EnvItemInfo> {
let mut v = vec![
&EnvItemInfo {
var: BLOCK_QUERY_CUTOFF,
description: "The block query cutoff time in seconds. This is the slot second after which requests will not be serviced. E.g. a value of 1 means that requests will not be serviced for the last second of any given slot.",
optional: false,
}, &EnvItemInfo {
var: BLOCK_QUERY_START,
description: "The block query start time in seconds. This is the slot second before which requests will not be serviced. E.g. a value of 1 means that requests will not be serviced for the first second of any given slot.",
optional: false,
}];
v.extend(SlotCalculator::inventory());
v
}

fn from_env() -> Result<Self, FromEnvErr<Self::Error>> {
let calc = SlotCalculator::from_env().map_err(FromEnvErr::from)?;
let block_query_cutoff = u8::from_env_var(BLOCK_QUERY_CUTOFF)
Expand Down
22 changes: 22 additions & 0 deletions src/utils/calc.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use crate::utils::from_env::{FromEnv, FromEnvErr, FromEnvVar};
use core::num;

use super::from_env::EnvItemInfo;

// Env vars
pub(crate) const START_TIMESTAMP: &str = "START_TIMESTAMP";
pub(crate) const SLOT_OFFSET: &str = "SLOT_OFFSET";
Expand Down Expand Up @@ -125,6 +127,26 @@ impl SlotCalculator {
impl FromEnv for SlotCalculator {
type Error = SlotCalcEnvError;

fn inventory() -> Vec<&'static EnvItemInfo> {
vec![
&EnvItemInfo {
var: START_TIMESTAMP,
description: "The start timestamp of the chain in seconds",
optional: false,
},
&EnvItemInfo {
var: SLOT_OFFSET,
description: "The slot offset of the chain in seconds",
optional: false,
},
&EnvItemInfo {
var: SLOT_DURATION,
description: "The slot duration of the chain in seconds",
optional: false,
},
]
}

fn from_env() -> Result<Self, FromEnvErr<Self::Error>> {
let start_timestamp = u64::from_env_var(START_TIMESTAMP)
.map_err(|e| e.map(SlotCalcEnvError::StartTimestamp))?;
Expand Down
40 changes: 40 additions & 0 deletions src/utils/from_env.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
use std::{convert::Infallible, env::VarError, num::ParseIntError, str::FromStr};

/// Details about an environment variable. This is used to generate
/// documentation for the environment variables and by the [`FromEnv`] trait to
/// check if necessary environment variables are present.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct EnvItemInfo {
/// The environment variable name.
pub var: &'static str,
/// A description of the environment variable function in the CFG.
pub description: &'static str,
/// Whether the environment variable is optional or not.
pub optional: bool,
}

/// Error type for loading from the environment. See the [`FromEnv`] trait for
/// more information.
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
Expand Down Expand Up @@ -93,6 +106,33 @@ pub trait FromEnv: core::fmt::Debug + Sized + 'static {
/// Error type produced when loading from the environment.
type Error: core::error::Error;

/// Get the required environment variable names for this type.
///
/// ## Note
///
/// This MUST include the environment variable names for all fields in the
/// struct, including optional vars.
fn inventory() -> Vec<&'static EnvItemInfo>;

/// Get a list of missing environment variables.
///
/// This will check all environment variables in the inventory, and return
/// a list of those that are non-optional and missing. This is useful for
/// reporting missing environment variables.
fn check_inventory() -> Result<(), Vec<&'static EnvItemInfo>> {
let mut missing = Vec::new();
for var in Self::inventory() {
if std::env::var(var.var).is_err() && !var.optional {
missing.push(var);
}
}
if missing.is_empty() {
Ok(())
} else {
Err(missing)
}
}

/// Load from the environment.
fn from_env() -> Result<Self, FromEnvErr<Self::Error>>;
}
Expand Down
10 changes: 10 additions & 0 deletions src/utils/metrics.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use crate::utils::from_env::{FromEnv, FromEnvErr, FromEnvVar};
use metrics_exporter_prometheus::PrometheusBuilder;

use super::from_env::EnvItemInfo;

/// Metrics port env var
const METRICS_PORT: &str = "METRICS_PORT";

Expand Down Expand Up @@ -40,6 +42,14 @@ impl From<u16> for MetricsConfig {
impl FromEnv for MetricsConfig {
type Error = std::num::ParseIntError;

fn inventory() -> Vec<&'static EnvItemInfo> {
vec![&EnvItemInfo {
var: METRICS_PORT,
description: "Port on which to serve metrics, u16, defaults to 9000",
optional: true,
}]
}

fn from_env() -> Result<Self, FromEnvErr<Self::Error>> {
match u16::from_env_var(METRICS_PORT).map(Self::from) {
Ok(cfg) => Ok(cfg),
Expand Down
28 changes: 27 additions & 1 deletion src/utils/otlp.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::utils::from_env::{FromEnv, FromEnvErr, FromEnvVar};
use crate::utils::from_env::{EnvItemInfo, FromEnv, FromEnvErr, FromEnvVar};
use opentelemetry::{trace::TracerProvider, KeyValue};
use opentelemetry_sdk::trace::SdkTracerProvider;
use opentelemetry_sdk::Resource;
Expand Down Expand Up @@ -110,6 +110,32 @@ pub struct OtelConfig {
impl FromEnv for OtelConfig {
type Error = url::ParseError;

fn inventory() -> Vec<&'static EnvItemInfo> {
vec![
&EnvItemInfo {
var: OTEL_ENDPOINT,
description:
"OTLP endpoint to send traces to, a url. If missing, disables OTLP exporting.",
optional: true,
},
&EnvItemInfo {
var: OTEL_LEVEL,
description: "OTLP level to export, defaults to DEBUG. Permissible values are: TRACE, DEBUG, INFO, WARN, ERROR, OFF",
optional: true,
},
&EnvItemInfo {
var: OTEL_TIMEOUT,
description: "OTLP timeout in milliseconds",
optional: true,
},
&EnvItemInfo {
var: OTEL_ENVIRONMENT,
description: "OTLP environment name, a string",
optional: true,
},
]
}

fn from_env() -> Result<Self, FromEnvErr<Self::Error>> {
// load endpoint from env. ignore empty values (shortcut return None), parse, and print the error if any using inspect_err
let endpoint = Url::from_env_var(OTEL_ENDPOINT).inspect_err(|e| eprintln!("{e}"))?;
Expand Down