From 0d17bb770be079e334cd152cb58b3f86caf8e0c7 Mon Sep 17 00:00:00 2001 From: Jules Wiriath Date: Wed, 15 Jul 2026 11:53:05 +0200 Subject: [PATCH 1/5] feat: added environment capability --- Cargo.lock | 1 + libdd-capabilities-impl/Cargo.toml | 1 + libdd-capabilities-impl/src/env.rs | 73 ++++++++++++++++++++++++++++++ libdd-capabilities-impl/src/lib.rs | 40 +++++++++++----- libdd-capabilities/src/env.rs | 30 ++++++++++++ libdd-capabilities/src/lib.rs | 2 + 6 files changed, 136 insertions(+), 11 deletions(-) create mode 100644 libdd-capabilities-impl/src/env.rs create mode 100644 libdd-capabilities/src/env.rs diff --git a/Cargo.lock b/Cargo.lock index 89335512f3..152bd66b66 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2878,6 +2878,7 @@ dependencies = [ name = "libdd-capabilities-impl" version = "3.0.0" dependencies = [ + "anyhow", "bytes", "http", "http-body-util", diff --git a/libdd-capabilities-impl/Cargo.toml b/libdd-capabilities-impl/Cargo.toml index f4da7baa45..a2310f51d8 100644 --- a/libdd-capabilities-impl/Cargo.toml +++ b/libdd-capabilities-impl/Cargo.toml @@ -16,6 +16,7 @@ crate-type = ["lib"] bench = false [dependencies] +anyhow = "1.0" bytes = "1" http = "1" libdd-capabilities = { path = "../libdd-capabilities", version = "2.1.0" } diff --git a/libdd-capabilities-impl/src/env.rs b/libdd-capabilities-impl/src/env.rs new file mode 100644 index 0000000000..b5b7f9fb6d --- /dev/null +++ b/libdd-capabilities-impl/src/env.rs @@ -0,0 +1,73 @@ +// Copyright 2026-Present Datadog, Inc. https://www.datadoghq.com/ +// SPDX-License-Identifier: Apache-2.0 + +//! Native environment-variable capability backed by `std::env`. + +use std::env::VarError; + +use libdd_capabilities::env::{EnvCapability, EnvError}; + +#[derive(Clone, Debug)] +pub struct NativeEnvCapability; + +impl EnvCapability for NativeEnvCapability { + fn new() -> Self { + Self + } + + fn get(&self, name: &str) -> Result, EnvError> { + match std::env::var(name) { + Ok(v) => Ok(Some(v)), + Err(VarError::NotPresent) => Ok(None), + Err(VarError::NotUnicode(_)) => Err(EnvError::NotUnicode(name.to_owned())), + } + } + + fn set(&self, name: &str, value: &str) -> Result<(), EnvError> { + std::env::set_var(name, value); + Ok(()) + } + + fn unset(&self, name: &str) -> Result<(), EnvError> { + std::env::remove_var(name); + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::sync::Mutex; + + // Serialize env-mutating tests; std::env is process-global. + static ENV_LOCK: Mutex<()> = Mutex::new(()); + + #[test] + fn get_absent_returns_none() { + let _g = ENV_LOCK.lock().unwrap(); + let cap = NativeEnvCapability; + // A name unlikely to be set in any environment. + assert_eq!(cap.get("LIBDD_CAP_TEST_ABSENT_XYZZY").unwrap(), None); + } + + #[test] + fn set_then_get_roundtrips() { + let _g = ENV_LOCK.lock().unwrap(); + let cap = NativeEnvCapability; + cap.set("LIBDD_CAP_TEST_SET", "value").unwrap(); + assert_eq!( + cap.get("LIBDD_CAP_TEST_SET").unwrap(), + Some("value".to_owned()) + ); + cap.unset("LIBDD_CAP_TEST_SET").unwrap(); + } + + #[test] + fn unset_then_get_returns_none() { + let _g = ENV_LOCK.lock().unwrap(); + let cap = NativeEnvCapability; + cap.set("LIBDD_CAP_TEST_UNSET", "value").unwrap(); + cap.unset("LIBDD_CAP_TEST_UNSET").unwrap(); + assert_eq!(cap.get("LIBDD_CAP_TEST_UNSET").unwrap(), None); + } +} diff --git a/libdd-capabilities-impl/src/lib.rs b/libdd-capabilities-impl/src/lib.rs index e2b48d7d74..1e2cb7e829 100644 --- a/libdd-capabilities-impl/src/lib.rs +++ b/libdd-capabilities-impl/src/lib.rs @@ -7,21 +7,25 @@ //! traits using platform-native backends (hyper for HTTP, tokio for sleep, //! etc.). Leaf crates (FFI, benchmarks) pin this type as the generic parameter. +pub mod env; mod http; pub mod sleep; use core::future::Future; use std::time::Duration; +pub use env::NativeEnvCapability; pub use http::NativeHttpClient; use libdd_capabilities::{http::HttpError, MaybeSend}; -pub use libdd_capabilities::{HttpClientCapability, LogWriterCapability, SleepCapability}; +pub use libdd_capabilities::{ + EnvCapability, EnvError, HttpClientCapability, LogWriterCapability, SleepCapability, +}; pub use sleep::NativeSleepCapability; /// Bundle struct for native platform capabilities. /// -/// Delegates to [`NativeHttpClient`] for HTTP and [`NativeSleepCapability`] for -/// sleep. Task spawning is handled internally by `SharedRuntime`. +/// Delegates to [`NativeHttpClient`] for HTTP and to unit-struct capabilities +/// for the rest. Task spawning is handled internally by `SharedRuntime`. /// /// Individual capability traits keep minimal per-function bounds (e.g. /// functions that only need HTTP require just `H: HttpClientCapability`, not the @@ -31,6 +35,7 @@ pub use sleep::NativeSleepCapability; pub struct NativeCapabilities { http: NativeHttpClient, sleep: NativeSleepCapability, + env: NativeEnvCapability, } impl Default for NativeCapabilities { @@ -44,16 +49,14 @@ impl NativeCapabilities { Self { http: NativeHttpClient::new_client(), sleep: NativeSleepCapability, + env: NativeEnvCapability, } } } impl HttpClientCapability for NativeCapabilities { fn new_client() -> Self { - Self { - http: NativeHttpClient::new_client(), - sleep: NativeSleepCapability, - } + Self::new() } fn request( @@ -78,13 +81,28 @@ impl LogWriterCapability for NativeCapabilities { impl SleepCapability for NativeCapabilities { fn new() -> Self { - Self { - http: NativeHttpClient::new_client(), - sleep: NativeSleepCapability, - } + Self::new() } fn sleep(&self, duration: Duration) -> impl Future + MaybeSend { self.sleep.sleep(duration) } } + +impl EnvCapability for NativeCapabilities { + fn new() -> Self { + Self::new() + } + + fn get(&self, name: &str) -> Result, EnvError> { + self.env.get(name) + } + + fn set(&self, name: &str, value: &str) -> Result<(), EnvError> { + self.env.set(name, value) + } + + fn unset(&self, name: &str) -> Result<(), EnvError> { + self.env.unset(name) + } +} diff --git a/libdd-capabilities/src/env.rs b/libdd-capabilities/src/env.rs new file mode 100644 index 0000000000..d3bdb22db9 --- /dev/null +++ b/libdd-capabilities/src/env.rs @@ -0,0 +1,30 @@ +// Copyright 2026-Present Datadog, Inc. https://www.datadoghq.com/ +// SPDX-License-Identifier: Apache-2.0 + +//! Environment-variable capability trait and error types. +//! +//! Sync: env access is a single map lookup on both native (`std::env`) and +//! wasm (`process.env`); adding a future would only add ceremony. + +#[derive(Debug, thiserror::Error)] +pub enum EnvError { + #[error("Env var value is not valid UTF-8: {0}")] + NotUnicode(String), + #[error("IO error: {0}")] + Io(anyhow::Error), +} + +pub trait EnvCapability: Clone + std::fmt::Debug { + fn new() -> Self; + + /// Read an env var. + /// + /// `Ok(None)` means the variable is unset; `Err(NotUnicode)` means it is + /// set but its value is not valid UTF-8. Callers that treat "missing" and + /// "invalid" the same should collapse both branches explicitly. + fn get(&self, name: &str) -> Result, EnvError>; + + fn set(&self, name: &str, value: &str) -> Result<(), EnvError>; + + fn unset(&self, name: &str) -> Result<(), EnvError>; +} diff --git a/libdd-capabilities/src/lib.rs b/libdd-capabilities/src/lib.rs index 1798946057..d4e5104b8e 100644 --- a/libdd-capabilities/src/lib.rs +++ b/libdd-capabilities/src/lib.rs @@ -3,12 +3,14 @@ //! Portable capability traits for cross-platform libdatadog. +pub mod env; pub mod http; pub mod log_output; pub mod maybe_send; pub mod sleep; pub mod spawn; +pub use self::env::{EnvCapability, EnvError}; pub use self::http::{HttpClientCapability, HttpError}; pub use self::log_output::LogWriterCapability; pub use self::sleep::SleepCapability; From 27390d0d751258696048d6a22a009bedc13dfee8 Mon Sep 17 00:00:00 2001 From: Jules Wiriath Date: Wed, 15 Jul 2026 14:58:27 +0200 Subject: [PATCH 2/5] feat!: make set and unset unsafe --- libdd-capabilities-impl/src/env.rs | 56 +++++++++++++++++++++++++----- libdd-capabilities-impl/src/lib.rs | 12 ++++--- libdd-capabilities/src/env.rs | 37 ++++++++++++++++++-- 3 files changed, 90 insertions(+), 15 deletions(-) diff --git a/libdd-capabilities-impl/src/env.rs b/libdd-capabilities-impl/src/env.rs index b5b7f9fb6d..552ff5d88a 100644 --- a/libdd-capabilities-impl/src/env.rs +++ b/libdd-capabilities-impl/src/env.rs @@ -5,7 +5,7 @@ use std::env::VarError; -use libdd_capabilities::env::{EnvCapability, EnvError}; +use libdd_capabilities::env::{validate_name, validate_value, EnvCapability, EnvError}; #[derive(Clone, Debug)] pub struct NativeEnvCapability; @@ -23,13 +23,26 @@ impl EnvCapability for NativeEnvCapability { } } - fn set(&self, name: &str, value: &str) -> Result<(), EnvError> { - std::env::set_var(name, value); + unsafe fn set(&self, name: &str, value: &str) -> Result<(), EnvError> { + validate_name(name)?; + validate_value(value)?; + // SAFETY: Caller upholds the single-threaded-env precondition + // documented on `EnvCapability::set`. Inputs have been validated so + // `std::env::set_var` will not panic. + unsafe { + std::env::set_var(name, value); + } Ok(()) } - fn unset(&self, name: &str) -> Result<(), EnvError> { - std::env::remove_var(name); + unsafe fn unset(&self, name: &str) -> Result<(), EnvError> { + validate_name(name)?; + // SAFETY: Caller upholds the single-threaded-env precondition + // documented on `EnvCapability::unset`. Name has been validated so + // `std::env::remove_var` will not panic. + unsafe { + std::env::remove_var(name); + } Ok(()) } } @@ -54,20 +67,45 @@ mod tests { fn set_then_get_roundtrips() { let _g = ENV_LOCK.lock().unwrap(); let cap = NativeEnvCapability; - cap.set("LIBDD_CAP_TEST_SET", "value").unwrap(); + unsafe { cap.set("LIBDD_CAP_TEST_SET", "value").unwrap() }; assert_eq!( cap.get("LIBDD_CAP_TEST_SET").unwrap(), Some("value".to_owned()) ); - cap.unset("LIBDD_CAP_TEST_SET").unwrap(); + unsafe { cap.unset("LIBDD_CAP_TEST_SET").unwrap() }; } #[test] fn unset_then_get_returns_none() { let _g = ENV_LOCK.lock().unwrap(); let cap = NativeEnvCapability; - cap.set("LIBDD_CAP_TEST_UNSET", "value").unwrap(); - cap.unset("LIBDD_CAP_TEST_UNSET").unwrap(); + unsafe { + cap.set("LIBDD_CAP_TEST_UNSET", "value").unwrap(); + cap.unset("LIBDD_CAP_TEST_UNSET").unwrap(); + } assert_eq!(cap.get("LIBDD_CAP_TEST_UNSET").unwrap(), None); } + + #[test] + fn set_rejects_invalid_name() { + let cap = NativeEnvCapability; + // SAFETY: validation fails before any env mutation happens. + unsafe { + assert!(matches!(cap.set("", "v"), Err(EnvError::Invalid(_)))); + assert!(matches!(cap.set("A=B", "v"), Err(EnvError::Invalid(_)))); + assert!(matches!(cap.set("A\0B", "v"), Err(EnvError::Invalid(_)))); + } + } + + #[test] + fn set_rejects_invalid_value() { + let cap = NativeEnvCapability; + // SAFETY: validation fails before any env mutation happens. + unsafe { + assert!(matches!( + cap.set("LIBDD_CAP_TEST_INVALID_VALUE", "a\0b"), + Err(EnvError::Invalid(_)) + )); + } + } } diff --git a/libdd-capabilities-impl/src/lib.rs b/libdd-capabilities-impl/src/lib.rs index 1e2cb7e829..9ecedfab58 100644 --- a/libdd-capabilities-impl/src/lib.rs +++ b/libdd-capabilities-impl/src/lib.rs @@ -98,11 +98,15 @@ impl EnvCapability for NativeCapabilities { self.env.get(name) } - fn set(&self, name: &str, value: &str) -> Result<(), EnvError> { - self.env.set(name, value) + unsafe fn set(&self, name: &str, value: &str) -> Result<(), EnvError> { + // SAFETY: forwarded verbatim; caller upholds `EnvCapability::set`'s + // single-threaded-env precondition. + unsafe { self.env.set(name, value) } } - fn unset(&self, name: &str) -> Result<(), EnvError> { - self.env.unset(name) + unsafe fn unset(&self, name: &str) -> Result<(), EnvError> { + // SAFETY: forwarded verbatim; caller upholds `EnvCapability::unset`'s + // single-threaded-env precondition. + unsafe { self.env.unset(name) } } } diff --git a/libdd-capabilities/src/env.rs b/libdd-capabilities/src/env.rs index d3bdb22db9..c9ee412d34 100644 --- a/libdd-capabilities/src/env.rs +++ b/libdd-capabilities/src/env.rs @@ -10,6 +10,8 @@ pub enum EnvError { #[error("Env var value is not valid UTF-8: {0}")] NotUnicode(String), + #[error("Invalid env var name or value: {0}")] + Invalid(&'static str), #[error("IO error: {0}")] Io(anyhow::Error), } @@ -24,7 +26,38 @@ pub trait EnvCapability: Clone + std::fmt::Debug { /// "invalid" the same should collapse both branches explicitly. fn get(&self, name: &str) -> Result, EnvError>; - fn set(&self, name: &str, value: &str) -> Result<(), EnvError>; + /// Set an env var. + /// + /// # Safety + /// No other thread may access the process environment concurrently. + unsafe fn set(&self, name: &str, value: &str) -> Result<(), EnvError>; + + /// Unset an env var. + /// + /// # Safety + /// No other thread may access the process environment concurrently. + unsafe fn unset(&self, name: &str) -> Result<(), EnvError>; +} + +/// Validate an env var name per the same rules `std::env::set_var` would +/// panic on: non-empty, no NUL byte, no `=` sign. +pub fn validate_name(name: &str) -> Result<(), EnvError> { + if name.is_empty() { + return Err(EnvError::Invalid("name is empty")); + } + if name.as_bytes().contains(&b'\0') { + return Err(EnvError::Invalid("name contains NUL byte")); + } + if name.as_bytes().contains(&b'=') { + return Err(EnvError::Invalid("name contains '=' character")); + } + Ok(()) +} - fn unset(&self, name: &str) -> Result<(), EnvError>; +/// Validate an env var value: no NUL byte (would panic in `std::env::set_var`). +pub fn validate_value(value: &str) -> Result<(), EnvError> { + if value.as_bytes().contains(&b'\0') { + return Err(EnvError::Invalid("value contains NUL byte")); + } + Ok(()) } From 6b5cd46b9579aa1862ae444807360d044bb514fa Mon Sep 17 00:00:00 2001 From: Jules Wiriath Date: Thu, 16 Jul 2026 16:20:00 +0200 Subject: [PATCH 3/5] revert: remove set/unset since they are too risky for libdatadog's kind of environment --- libdd-capabilities-impl/src/env.rs | 76 +----------------------------- libdd-capabilities-impl/src/lib.rs | 12 ----- libdd-capabilities/src/env.rs | 39 +-------------- 3 files changed, 2 insertions(+), 125 deletions(-) diff --git a/libdd-capabilities-impl/src/env.rs b/libdd-capabilities-impl/src/env.rs index 552ff5d88a..8bb6963019 100644 --- a/libdd-capabilities-impl/src/env.rs +++ b/libdd-capabilities-impl/src/env.rs @@ -5,7 +5,7 @@ use std::env::VarError; -use libdd_capabilities::env::{validate_name, validate_value, EnvCapability, EnvError}; +use libdd_capabilities::env::{EnvCapability, EnvError}; #[derive(Clone, Debug)] pub struct NativeEnvCapability; @@ -22,90 +22,16 @@ impl EnvCapability for NativeEnvCapability { Err(VarError::NotUnicode(_)) => Err(EnvError::NotUnicode(name.to_owned())), } } - - unsafe fn set(&self, name: &str, value: &str) -> Result<(), EnvError> { - validate_name(name)?; - validate_value(value)?; - // SAFETY: Caller upholds the single-threaded-env precondition - // documented on `EnvCapability::set`. Inputs have been validated so - // `std::env::set_var` will not panic. - unsafe { - std::env::set_var(name, value); - } - Ok(()) - } - - unsafe fn unset(&self, name: &str) -> Result<(), EnvError> { - validate_name(name)?; - // SAFETY: Caller upholds the single-threaded-env precondition - // documented on `EnvCapability::unset`. Name has been validated so - // `std::env::remove_var` will not panic. - unsafe { - std::env::remove_var(name); - } - Ok(()) - } } #[cfg(test)] mod tests { use super::*; - use std::sync::Mutex; - - // Serialize env-mutating tests; std::env is process-global. - static ENV_LOCK: Mutex<()> = Mutex::new(()); #[test] fn get_absent_returns_none() { - let _g = ENV_LOCK.lock().unwrap(); let cap = NativeEnvCapability; // A name unlikely to be set in any environment. assert_eq!(cap.get("LIBDD_CAP_TEST_ABSENT_XYZZY").unwrap(), None); } - - #[test] - fn set_then_get_roundtrips() { - let _g = ENV_LOCK.lock().unwrap(); - let cap = NativeEnvCapability; - unsafe { cap.set("LIBDD_CAP_TEST_SET", "value").unwrap() }; - assert_eq!( - cap.get("LIBDD_CAP_TEST_SET").unwrap(), - Some("value".to_owned()) - ); - unsafe { cap.unset("LIBDD_CAP_TEST_SET").unwrap() }; - } - - #[test] - fn unset_then_get_returns_none() { - let _g = ENV_LOCK.lock().unwrap(); - let cap = NativeEnvCapability; - unsafe { - cap.set("LIBDD_CAP_TEST_UNSET", "value").unwrap(); - cap.unset("LIBDD_CAP_TEST_UNSET").unwrap(); - } - assert_eq!(cap.get("LIBDD_CAP_TEST_UNSET").unwrap(), None); - } - - #[test] - fn set_rejects_invalid_name() { - let cap = NativeEnvCapability; - // SAFETY: validation fails before any env mutation happens. - unsafe { - assert!(matches!(cap.set("", "v"), Err(EnvError::Invalid(_)))); - assert!(matches!(cap.set("A=B", "v"), Err(EnvError::Invalid(_)))); - assert!(matches!(cap.set("A\0B", "v"), Err(EnvError::Invalid(_)))); - } - } - - #[test] - fn set_rejects_invalid_value() { - let cap = NativeEnvCapability; - // SAFETY: validation fails before any env mutation happens. - unsafe { - assert!(matches!( - cap.set("LIBDD_CAP_TEST_INVALID_VALUE", "a\0b"), - Err(EnvError::Invalid(_)) - )); - } - } } diff --git a/libdd-capabilities-impl/src/lib.rs b/libdd-capabilities-impl/src/lib.rs index 9ecedfab58..68c1854ff6 100644 --- a/libdd-capabilities-impl/src/lib.rs +++ b/libdd-capabilities-impl/src/lib.rs @@ -97,16 +97,4 @@ impl EnvCapability for NativeCapabilities { fn get(&self, name: &str) -> Result, EnvError> { self.env.get(name) } - - unsafe fn set(&self, name: &str, value: &str) -> Result<(), EnvError> { - // SAFETY: forwarded verbatim; caller upholds `EnvCapability::set`'s - // single-threaded-env precondition. - unsafe { self.env.set(name, value) } - } - - unsafe fn unset(&self, name: &str) -> Result<(), EnvError> { - // SAFETY: forwarded verbatim; caller upholds `EnvCapability::unset`'s - // single-threaded-env precondition. - unsafe { self.env.unset(name) } - } } diff --git a/libdd-capabilities/src/env.rs b/libdd-capabilities/src/env.rs index c9ee412d34..ad69d42368 100644 --- a/libdd-capabilities/src/env.rs +++ b/libdd-capabilities/src/env.rs @@ -4,14 +4,12 @@ //! Environment-variable capability trait and error types. //! //! Sync: env access is a single map lookup on both native (`std::env`) and -//! wasm (`process.env`); adding a future would only add ceremony. +//! wasm (`process.env`). #[derive(Debug, thiserror::Error)] pub enum EnvError { #[error("Env var value is not valid UTF-8: {0}")] NotUnicode(String), - #[error("Invalid env var name or value: {0}")] - Invalid(&'static str), #[error("IO error: {0}")] Io(anyhow::Error), } @@ -25,39 +23,4 @@ pub trait EnvCapability: Clone + std::fmt::Debug { /// set but its value is not valid UTF-8. Callers that treat "missing" and /// "invalid" the same should collapse both branches explicitly. fn get(&self, name: &str) -> Result, EnvError>; - - /// Set an env var. - /// - /// # Safety - /// No other thread may access the process environment concurrently. - unsafe fn set(&self, name: &str, value: &str) -> Result<(), EnvError>; - - /// Unset an env var. - /// - /// # Safety - /// No other thread may access the process environment concurrently. - unsafe fn unset(&self, name: &str) -> Result<(), EnvError>; -} - -/// Validate an env var name per the same rules `std::env::set_var` would -/// panic on: non-empty, no NUL byte, no `=` sign. -pub fn validate_name(name: &str) -> Result<(), EnvError> { - if name.is_empty() { - return Err(EnvError::Invalid("name is empty")); - } - if name.as_bytes().contains(&b'\0') { - return Err(EnvError::Invalid("name contains NUL byte")); - } - if name.as_bytes().contains(&b'=') { - return Err(EnvError::Invalid("name contains '=' character")); - } - Ok(()) -} - -/// Validate an env var value: no NUL byte (would panic in `std::env::set_var`). -pub fn validate_value(value: &str) -> Result<(), EnvError> { - if value.as_bytes().contains(&b'\0') { - return Err(EnvError::Invalid("value contains NUL byte")); - } - Ok(()) } From df2e0f1cc07ff932b9629086b346bc7fd7b2eab8 Mon Sep 17 00:00:00 2001 From: Jules Wiriath Date: Thu, 16 Jul 2026 17:07:22 +0200 Subject: [PATCH 4/5] fix: better error message --- libdd-capabilities/src/env.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libdd-capabilities/src/env.rs b/libdd-capabilities/src/env.rs index ad69d42368..69d3abe04a 100644 --- a/libdd-capabilities/src/env.rs +++ b/libdd-capabilities/src/env.rs @@ -8,7 +8,7 @@ #[derive(Debug, thiserror::Error)] pub enum EnvError { - #[error("Env var value is not valid UTF-8: {0}")] + #[error("The value of the environment variable `{0}` is not valid UTF-8")] NotUnicode(String), #[error("IO error: {0}")] Io(anyhow::Error), From 5fedbee95a52caedb6458e16f408ba30d6b75450 Mon Sep 17 00:00:00 2001 From: Jules Wiriath Date: Thu, 16 Jul 2026 18:04:55 +0200 Subject: [PATCH 5/5] docs: explain why set and unset are excluded --- libdd-capabilities/src/env.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/libdd-capabilities/src/env.rs b/libdd-capabilities/src/env.rs index 69d3abe04a..f464f74a2c 100644 --- a/libdd-capabilities/src/env.rs +++ b/libdd-capabilities/src/env.rs @@ -5,6 +5,11 @@ //! //! Sync: env access is a single map lookup on both native (`std::env`) and //! wasm (`process.env`). +//! +//! `set` and `unset` are intentionally absent from this trait. libdatadog is +//! embedded in many kinds of runtime, where mutating the process environment +//! very much unsafe. Exposing mutation here would make it trivially easy for +//! callers to corrupt the environment of a multi-threaded host process. #[derive(Debug, thiserror::Error)] pub enum EnvError {