Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ pkg/
target/
.wrangler/

# Node
node_modules/
Comment thread
prk-Jr marked this conversation as resolved.
Outdated

# env
.env

Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

107 changes: 107 additions & 0 deletions crates/edgezero-adapter-axum/src/config_store.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
//! Axum adapter config store: env vars with in-memory defaults fallback.

use std::collections::HashMap;

use edgezero_core::config_store::ConfigStore;

/// Config store for local dev / Axum. Reads from env vars with manifest
/// defaults as fallback. Env vars take precedence over defaults.
///
/// # Note on `from_env`
///
/// [`AxumConfigStore::from_env`] snapshots the **entire** process environment
/// at construction time. Any env var name is therefore accessible via
/// `ctx.config_store()?.get("VAR_NAME")`. In practice, manifest config keys
/// use lowercase dotted names (e.g. `feature.new_checkout`) which do not
/// collide with typical uppercase process vars (`PATH`, `HOME`, etc.), so
/// accidental leakage is unlikely. For production deployments use Fastly or
/// Cloudflare adapters, which read only from their respective platform stores.
pub struct AxumConfigStore {
env: HashMap<String, String>,
defaults: HashMap<String, String>,
}

impl AxumConfigStore {
/// Create from env vars and optional manifest defaults.
pub fn new(
env: impl IntoIterator<Item = (String, String)>,
defaults: impl IntoIterator<Item = (String, String)>,
) -> Self {
Self {
env: env.into_iter().collect(),
defaults: defaults.into_iter().collect(),
}
}

/// Create from the current process environment and manifest defaults.
pub fn from_env(defaults: impl IntoIterator<Item = (String, String)>) -> Self {
Self::new(std::env::vars(), defaults)
Comment thread
prk-Jr marked this conversation as resolved.
Outdated
}
}

impl ConfigStore for AxumConfigStore {
fn get(&self, key: &str) -> Option<String> {
self.env
.get(key)
.or_else(|| self.defaults.get(key))
.cloned()
}
}

#[cfg(test)]
mod tests {
use super::*;

fn store(env: &[(&str, &str)], defaults: &[(&str, &str)]) -> AxumConfigStore {
AxumConfigStore::new(
env.iter().map(|(k, v)| (k.to_string(), v.to_string())),
defaults.iter().map(|(k, v)| (k.to_string(), v.to_string())),
)
}

#[test]
fn axum_config_store_returns_values() {
let s = store(&[("MY_KEY", "my_val")], &[]);
assert_eq!(s.get("MY_KEY"), Some("my_val".to_string()));
}

#[test]
fn axum_config_store_returns_none_for_missing() {
let s = store(&[], &[]);
assert_eq!(s.get("NOPE"), None);
}

#[test]
fn axum_config_store_env_overrides_defaults() {
let s = store(&[("KEY", "from_env")], &[("KEY", "from_default")]);
assert_eq!(s.get("KEY"), Some("from_env".to_string()));
}

#[test]
fn axum_config_store_falls_back_to_defaults() {
let s = store(&[], &[("KEY", "default_val")]);
assert_eq!(s.get("KEY"), Some("default_val".to_string()));
}

// Run the shared contract tests against AxumConfigStore (env path).
edgezero_core::config_store_contract_tests!(axum_config_store_env_contract, {
AxumConfigStore::new(
[
("contract.key.a".to_string(), "value_a".to_string()),
("contract.key.b".to_string(), "value_b".to_string()),
],
[],
)
});

// Run the shared contract tests against AxumConfigStore (defaults path).
edgezero_core::config_store_contract_tests!(axum_config_store_defaults_contract, {
AxumConfigStore::new(
[],
[
("contract.key.a".to_string(), "value_a".to_string()),
("contract.key.b".to_string(), "value_b".to_string()),
],
)
});
}
49 changes: 41 additions & 8 deletions crates/edgezero-adapter-axum/src/dev_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ use tokio::signal;
use tower::{service_fn, Service};

use edgezero_core::app::Hooks;
use edgezero_core::config_store::ConfigStoreHandle;
use edgezero_core::manifest::ManifestLoader;
use edgezero_core::router::RouterService;
use log::LevelFilter;
use simple_logger::SimpleLogger;

use crate::config_store::AxumConfigStore;
use crate::service::EdgeZeroAxumService;

/// Configuration used when running the dev server embedding EdgeZero into Axum.
Expand All @@ -34,18 +36,30 @@ impl Default for AxumDevServerConfig {
pub struct AxumDevServer {
router: RouterService,
config: AxumDevServerConfig,
config_store_handle: Option<ConfigStoreHandle>,
}

impl AxumDevServer {
pub fn new(router: RouterService) -> Self {
Self {
router,
config: AxumDevServerConfig::default(),
config_store_handle: None,
}
}

pub fn with_config(router: RouterService, config: AxumDevServerConfig) -> Self {
Self { router, config }
Self {
router,
config,
config_store_handle: None,
}
}

#[must_use]
pub fn with_config_store(mut self, handle: ConfigStoreHandle) -> Self {
self.config_store_handle = Some(handle);
self
}

pub fn run(self) -> anyhow::Result<()> {
Expand All @@ -58,7 +72,11 @@ impl AxumDevServer {
}

async fn run_async(self) -> anyhow::Result<()> {
let AxumDevServer { router, config } = self;
let AxumDevServer {
router,
config,
config_store_handle,
} = self;

// Allow binding to already-open listener if caller created one to surface errors early.
let listener = StdTcpListener::bind(config.addr)
Expand All @@ -70,22 +88,30 @@ impl AxumDevServer {
let listener = tokio::net::TcpListener::from_std(listener)
.context("failed to adopt std listener into tokio")?;

serve_with_listener(router, listener, config.enable_ctrl_c).await
serve_with_listener(router, listener, config.enable_ctrl_c, config_store_handle).await
}

#[cfg(test)]
async fn run_with_listener(self, listener: tokio::net::TcpListener) -> anyhow::Result<()> {
let AxumDevServer { router, config } = self;
serve_with_listener(router, listener, config.enable_ctrl_c).await
let AxumDevServer {
router,
config,
config_store_handle,
} = self;
serve_with_listener(router, listener, config.enable_ctrl_c, config_store_handle).await
}
}

async fn serve_with_listener(
router: RouterService,
listener: tokio::net::TcpListener,
enable_ctrl_c: bool,
config_store_handle: Option<ConfigStoreHandle>,
Comment thread
aram356 marked this conversation as resolved.
Outdated
Comment thread
prk-Jr marked this conversation as resolved.
Outdated
) -> anyhow::Result<()> {
let service = EdgeZeroAxumService::new(router);
let mut service = EdgeZeroAxumService::new(router);
if let Some(handle) = config_store_handle {
service = service.with_config_store_handle(handle);
}
let router = Router::new().fallback_service(service_fn(move |req| {
let mut svc = service.clone();
async move { svc.call(req).await }
Expand Down Expand Up @@ -113,7 +139,8 @@ async fn serve_with_listener(

pub fn run_app<A: Hooks>(manifest_src: &str) -> anyhow::Result<()> {
let manifest = ManifestLoader::load_from_str(manifest_src);
let logging = manifest.manifest().logging_or_default("axum");
let m = manifest.manifest();
let logging = m.logging_or_default(edgezero_core::app::AXUM_ADAPTER);

let level: LevelFilter = logging.level.into();
let level = if logging.echo_stdout.unwrap_or(true) {
Expand All @@ -127,7 +154,13 @@ pub fn run_app<A: Hooks>(manifest_src: &str) -> anyhow::Result<()> {
let app = A::build_app();
let router = app.router().clone();

AxumDevServer::new(router).run()
let mut server = AxumDevServer::new(router);
if let Some(cfg) = m.stores.config.as_ref() {
let defaults = cfg.config_store_defaults().clone();
let store = AxumConfigStore::from_env(defaults);
Comment thread
prk-Jr marked this conversation as resolved.
Outdated
server = server.with_config_store(ConfigStoreHandle::new(std::sync::Arc::new(store)));
}
server.run()
}

#[cfg(test)]
Expand Down
4 changes: 4 additions & 0 deletions crates/edgezero-adapter-axum/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
//! Axum adapter for EdgeZero routers and applications.

#[cfg(feature = "axum")]
pub mod config_store;
#[cfg(feature = "axum")]
mod context;
#[cfg(feature = "axum")]
Expand All @@ -16,6 +18,8 @@ mod service;
#[cfg(feature = "cli")]
pub mod cli;

#[cfg(feature = "axum")]
pub use config_store::AxumConfigStore;
#[cfg(feature = "axum")]
pub use context::AxumRequestContext;
#[cfg(feature = "axum")]
Expand Down
Loading