blob: f5459b915298ba726990c922772b33e2accfa77a [file] [log] [blame]
use std::{error::Error, fmt::Debug};
use config::{
builder::AsyncState, AsyncSource, ConfigBuilder, ConfigError, FileFormat, Format, Map,
};
use async_trait::async_trait;
use futures::{select, FutureExt};
use warp::Filter;
// Example below presents sample configuration server and client.
//
// Server serves simple configuration on HTTP endpoint.
// Client consumes it using custom HTTP AsyncSource built on top of reqwest.
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
select! {
r = run_server().fuse() => r,
r = run_client().fuse() => r
}
}
async fn run_server() -> Result<(), Box<dyn Error>> {
let service = warp::path("configuration").map(|| r#"{ "value" : 123 }"#);
println!("Running server on localhost:5001");
warp::serve(service).bind(([127, 0, 0, 1], 5001)).await;
Ok(())
}
async fn run_client() -> Result<(), Box<dyn Error>> {
// Good enough for an example to allow server to start
tokio::time::sleep(tokio::time::Duration::from_secs(3)).await;
let config = ConfigBuilder::<AsyncState>::default()
.add_async_source(HttpSource {
uri: "http://localhost:5001/configuration".into(),
format: FileFormat::Json,
})
.build()
.await?;
println!("Config value is {}", config.get::<String>("value")?);
Ok(())
}
// Actual implementation of AsyncSource can be found below
#[derive(Debug)]
struct HttpSource<F: Format> {
uri: String,
format: F,
}
#[async_trait]
impl<F: Format + Send + Sync + Debug> AsyncSource for HttpSource<F> {
async fn collect(&self) -> Result<Map<String, config::Value>, ConfigError> {
reqwest::get(&self.uri)
.await
.map_err(|e| ConfigError::Foreign(Box::new(e)))? // error conversion is possible from custom AsyncSource impls
.text()
.await
.map_err(|e| ConfigError::Foreign(Box::new(e)))
.and_then(|text| {
self.format
.parse(Some(&self.uri), &text)
.map_err(|e| ConfigError::Foreign(e))
})
}
}