vne/lib.rs
1//! # vne
2//!
3//!> [!WARNING]
4//!> This is just an proof of concept of a stupid idea I had. It is probably not ready for use.
5//!
6//!Environment variables.
7//!
8//!## Usage
9//!
10//!You can setup `vne` in your `main.rs` like the following:
11//!
12//!```rust
13//!fn main() {
14//! // This will ensure all variables declared by a usage of `vne!` exist.
15//! ENV.validate().unwrap();
16//!
17//! // The rest of your code
18//! let testing: String = vne::vne!("TESTING");
19//!}
20//!
21//!// It is *really* important this is in `main.rs` and at the bottom of the file!
22//!const ENV: vne::Environment = vne::build!();
23//!```
24//!
25//!## Framework integration
26//!
27//!You can statically analyze the environment variables that exist within your project. This could be used by a framework to show warning at deploy time for example. You can read the `target/vne` file and parse it with in the following format:
28//!
29//!```text
30//!{time}\n
31//!# The following repeats
32//!{crate_name}\t{bin_name}\t{env_1}\t{env_2}\n
33//!```
34//!
35//!## Inspiration
36//!
37//!We make use of the pattern which I originally came across in [macro_state](https://github.com/sam0x17/macro_state).
38#![cfg_attr(docsrs, feature(doc_cfg))]
39
40use std::{path::PathBuf, sync::atomic::AtomicBool};
41
42pub use vne_macros::{build, vne};
43
44static HAS_VALIDATED: AtomicBool = AtomicBool::new(false);
45
46pub struct Environment {
47 envs: &'static [&'static str],
48}
49
50impl Environment {
51 // TODO: Error handling
52 pub fn load(self, path: impl Into<PathBuf>) -> Self {
53 let path = path.into();
54
55 // TODO: Handle `.env.example`, `.env.local`, etc
56 // TODO: Allow `path` to be to directory or direct file
57
58 if path.join(".env").exists() {
59 let raw = std::fs::read_to_string(path.join(".env"))
60 .expect("vne error: failed to read environment file");
61
62 let envs = raw
63 .lines()
64 .map(|v| v.trim())
65 .filter(|v| !v.is_empty())
66 .filter(|v| !v.starts_with('#'))
67 .map(|v| v.splitn(2, "="))
68 .map(|mut v| (v.next().unwrap(), v.next().unwrap().trim_matches('"')));
69
70 for (key, value) in envs {
71 if std::env::var(key).is_err() {
72 std::env::set_var(key, value);
73 }
74 }
75 } else {
76 panic!("vne error: failed to find .env file")
77 }
78
79 self
80 }
81
82 // TODO: Proper error type
83 pub fn validate(&self) -> Result<(), String> {
84 for env in self.envs {
85 if std::env::var(env).is_err() {
86 return Err(format!("vne error: missing environment variable {env:?}"));
87 }
88 }
89
90 HAS_VALIDATED.store(true, std::sync::atomic::Ordering::SeqCst);
91 Ok(())
92 }
93}
94
95#[doc(hidden)]
96pub mod internal {
97 use super::*;
98
99 pub const fn construct_env(envs: &'static [&'static str]) -> Environment {
100 Environment { envs }
101 }
102
103 pub fn from_env(key: &'static str) -> String {
104 if !HAS_VALIDATED.load(std::sync::atomic::Ordering::SeqCst) {
105 panic!("vne error: attempted to access environment variable before validation was run");
106 }
107
108 std::env::var(key).expect("vne error: unreachable failed to get env")
109 }
110}