1#![cfg_attr(docsrs, feature(doc_cfg))]
5#![doc(html_favicon_url = "https://nil.dev.br/favicon.png")]
6#![feature(const_clone, const_cmp, derive_const)]
7
8use anyhow::{Error, Result, anyhow};
9use serde::{Deserialize, Serialize};
10use std::env;
11use std::ffi::OsStr;
12use std::num::NonZeroU16;
13use std::path::Path;
14use strum::{AsRefStr, Display, EnumString};
15use url::Url;
16
17#[cfg(feature = "typescript")]
18use ts_rs::TS;
19
20#[derive(Copy, Debug, Display, AsRefStr, EnumString, Deserialize, Serialize)]
21#[derive_const(Clone, PartialEq, Eq)]
22#[cfg_attr(feature = "typescript", derive(TS))]
23#[cfg_attr(feature = "typescript", ts(export, repr(enum = name)))]
24#[cfg_attr(feature = "typescript", ts(rename = "env_Var"))]
25pub enum Var {
26 #[serde(rename = "NIL_DATABASE_URL")]
27 #[strum(serialize = "NIL_DATABASE_URL")]
28 DatabaseUrl,
29
30 #[serde(rename = "NIL_JWT_SECRET")]
31 #[strum(serialize = "NIL_JWT_SECRET")]
32 JwtSecret,
33
34 #[serde(rename = "NIL_LOG_DIR")]
35 #[strum(serialize = "NIL_LOG_DIR")]
36 LogDir,
37
38 #[serde(rename = "NIL_LOG_LEVEL")]
39 #[strum(serialize = "NIL_LOG_LEVEL")]
40 LogLevel,
41
42 #[serde(rename = "NIL_LOG_TOWER_HTTP")]
43 #[strum(serialize = "NIL_LOG_TOWER_HTTP")]
44 LogTowerHttp,
45
46 #[serde(rename = "NIL_MINIFY_SOURCE")]
47 #[strum(serialize = "NIL_MINIFY_SOURCE")]
48 MinifySource,
49
50 #[serde(rename = "NIL_REMOTE_SERVER_ADDR")]
51 #[strum(serialize = "NIL_REMOTE_SERVER_ADDR")]
52 RemoteServerAddr,
53
54 #[serde(rename = "NIL_REMOTE_WORLD_LIMIT")]
55 #[strum(serialize = "NIL_REMOTE_WORLD_LIMIT")]
56 RemoteWorldLimit,
57
58 #[serde(rename = "NIL_REMOTE_WORLD_LIMIT_PER_USER")]
59 #[strum(serialize = "NIL_REMOTE_WORLD_LIMIT_PER_USER")]
60 RemoteWorldLimitPerUser,
61}
62
63impl Var {
64 pub unsafe fn set(self, value: impl AsRef<OsStr>) {
68 unsafe { env::set_var(self, value) }
69 }
70
71 pub unsafe fn remove(self) {
75 unsafe { env::remove_var(self) }
76 }
77}
78
79impl AsRef<OsStr> for Var {
80 fn as_ref(&self) -> &OsStr {
81 OsStr::new(<Var as AsRef<str>>::as_ref(self))
82 }
83}
84
85pub fn database_url() -> Result<Box<str>> {
86 env::var(Var::DatabaseUrl)
87 .map(String::into_boxed_str)
88 .map_err(|_| on_var_err(Var::DatabaseUrl))
89}
90
91pub fn jwt_secret() -> Box<str> {
92 env::var(Var::JwtSecret)
94 .map(String::into_boxed_str)
95 .unwrap_or_else(|_| Box::from("CALL-OF-NIL"))
96}
97
98pub fn log_dir() -> Result<Box<Path>> {
99 env::var(Var::LogDir)
100 .map(|dir| Box::from(Path::new(&dir)))
101 .map_err(|_| on_var_err(Var::LogDir))
102}
103
104pub fn log_level() -> Box<str> {
105 env::var(Var::LogLevel)
106 .map(String::into_boxed_str)
107 .unwrap_or_else(|_| Box::from("trace"))
108}
109
110pub fn log_tower_http() -> bool {
111 env::var(Var::LogTowerHttp).is_ok_and(|it| it == "true")
112}
113
114pub fn remote_server_addr() -> Url {
115 if let Ok(addr) = env::var(Var::RemoteServerAddr)
116 && let Ok(url) = Url::parse(&addr)
117 {
118 url
119 } else {
120 Url::parse("https://tsukilabs.dev.br/nil/").unwrap()
121 }
122}
123
124pub fn remote_world_limit() -> NonZeroU16 {
125 env::var(Var::RemoteWorldLimit)
126 .ok()
127 .and_then(|it| it.parse::<NonZeroU16>().ok())
128 .unwrap_or_else(|| unsafe { NonZeroU16::new_unchecked(100) })
129}
130
131pub fn remote_world_limit_per_user() -> NonZeroU16 {
132 env::var(Var::RemoteWorldLimitPerUser)
133 .ok()
134 .and_then(|it| it.parse::<NonZeroU16>().ok())
135 .unwrap_or_else(|| unsafe { NonZeroU16::new_unchecked(3) })
136}
137
138pub(crate) fn on_var_err(var: Var) -> Error {
139 anyhow!("environment variable not found: {var}")
140}