-
Notifications
You must be signed in to change notification settings - Fork 328
Per-endpoint configuration #109
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 13 commits
4f9bdd5
af5d370
7ecfafe
5b54fc6
2e4c928
ce58d52
e47776f
e5b906d
f3789d7
702a4e6
959713d
06cfc45
5329b01
3f1797e
2eb5dfc
c00e87b
c9dd3bb
b7ced5a
9255d0a
b8ada8f
1c80255
5bc825a
85e5eb6
a3cb7ff
73769de
262985a
e705b58
34486c2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
#![feature(async_await, futures_api)] | ||
|
||
use tide::{head::Path, ExtractConfiguration}; | ||
|
||
/// A type that represents how much value will be added by the `add` handler. | ||
#[derive(Clone, Default)] | ||
struct IncreaseBy(i32); | ||
|
||
async fn add( | ||
Path(base): Path<i32>, | ||
// `ExtractConfiguration` will extract the configuration item of given type, and provide it as | ||
// `Option<T>`. If it is not set, the inner value will be `None`. | ||
ExtractConfiguration(amount): ExtractConfiguration<IncreaseBy>, | ||
bIgBV marked this conversation as resolved.
Show resolved
Hide resolved
|
||
) -> String { | ||
let IncreaseBy(amount) = amount.unwrap_or_default(); | ||
format!("{} plus {} is {}", base, amount, base + amount) | ||
} | ||
|
||
fn main() { | ||
let mut app = tide::App::new(()); | ||
// `App::config` sets the default configuration of the app (that is, a top-level router). | ||
app.config(IncreaseBy(1)); | ||
app.at("add_one/{}").get(add); // `IncreaseBy` is set to 1 | ||
app.at("add_two/{}").get(add).config(IncreaseBy(2)); // `IncreaseBy` is overridden to 2 | ||
|
||
let address = "127.0.0.1:8000".to_owned(); | ||
println!("Server is listening on http://{}", address); | ||
app.serve(address); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
//! Types for managing and extracting configuration. | ||
|
||
use std::any::{Any, TypeId}; | ||
use std::collections::HashMap; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A possible performance improvement here would be to use the hashbrown crate. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Isn't that currently being evaluated to be used as the default HashMap implementation in the std? If that's the case then do you think it's a good idea to replace it right now? (Though to be fair, it might be at least a few releases away..) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I heard that hashbrown is used only in rustc, not libstd. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh I missed this. But yeah, it's landing in libstd; people made a PR during RustFest Rome for this! |
||
|
||
use futures::future::FutureObj; | ||
|
||
use crate::{Extract, Request, Response, RouteMatch}; | ||
|
||
trait ConfigurationItem: Any + Send + Sync { | ||
fn clone_any(&self) -> Box<dyn ConfigurationItem>; | ||
fn as_dyn_any(&self) -> &(dyn Any + Send + Sync); | ||
fn as_dyn_any_mut(&mut self) -> &mut (dyn Any + Send + Sync); | ||
} | ||
|
||
impl<T> ConfigurationItem for T | ||
where | ||
T: Any + Clone + Send + Sync, | ||
{ | ||
fn clone_any(&self) -> Box<dyn ConfigurationItem> { | ||
Box::new(self.clone()) | ||
} | ||
|
||
fn as_dyn_any(&self) -> &(dyn Any + Send + Sync) { | ||
self | ||
} | ||
|
||
fn as_dyn_any_mut(&mut self) -> &mut (dyn Any + Send + Sync) { | ||
self | ||
} | ||
} | ||
|
||
impl Clone for Box<dyn ConfigurationItem> { | ||
fn clone(&self) -> Box<dyn ConfigurationItem> { | ||
(&**self).clone_any() | ||
} | ||
} | ||
|
||
/// A cloneable typemap for saving per-endpoint configuration. | ||
/// | ||
/// Configuration is mostly managed by `App` and `Router`, so this is normally not used directly. | ||
#[derive(Clone)] | ||
yoshuawuyts marked this conversation as resolved.
Show resolved
Hide resolved
|
||
pub struct Configuration(HashMap<TypeId, Box<dyn ConfigurationItem>>); | ||
|
||
impl Configuration { | ||
pub(crate) fn new() -> Self { | ||
Configuration(HashMap::new()) | ||
} | ||
|
||
pub(crate) fn merge(&mut self, base: &Configuration) { | ||
let overlay = std::mem::replace(&mut self.0, base.0.clone()); | ||
self.0.extend(overlay); | ||
} | ||
|
||
/// Retrieve the configuration item of given type, returning `None` if it is not found. | ||
pub fn read<T: Any + Clone + Send + Sync>(&self) -> Option<&T> { | ||
let id = TypeId::of::<T>(); | ||
self.0 | ||
.get(&id) | ||
.and_then(|v| (**v).as_dyn_any().downcast_ref::<T>()) | ||
} | ||
|
||
/// Save the given configuration item. | ||
pub fn write<T: Any + Clone + Send + Sync>(&mut self, value: T) { | ||
let id = TypeId::of::<T>(); | ||
self.0 | ||
.insert(id, Box::new(value) as Box<dyn ConfigurationItem>); | ||
} | ||
} | ||
|
||
/// An extractor for reading configuration from endpoints. | ||
/// | ||
/// It will try to retrieve the given configuration item. If it is not set, the extracted value | ||
/// will be `None`. | ||
bIgBV marked this conversation as resolved.
Show resolved
Hide resolved
|
||
pub struct ExtractConfiguration<T>(pub Option<T>); | ||
|
||
impl<S: 'static, T: Any + Clone + Send + Sync + 'static> Extract<S> for ExtractConfiguration<T> { | ||
type Fut = FutureObj<'static, Result<Self, Response>>; | ||
|
||
fn extract( | ||
data: &mut S, | ||
req: &mut Request, | ||
params: &Option<RouteMatch<'_>>, | ||
config: &Configuration, | ||
) -> Self::Fut { | ||
let config_item = config.read().cloned(); | ||
FutureObj::new(Box::new( | ||
async move { Ok(ExtractConfiguration(config_item)) }, | ||
)) | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
|
||
#[test] | ||
fn configuration_read_write() { | ||
let mut config = Configuration::new(); | ||
assert_eq!(config.read::<usize>(), None); | ||
assert_eq!(config.read::<isize>(), None); | ||
config.write(42usize); | ||
config.write(-3isize); | ||
assert_eq!(config.read::<usize>(), Some(&42)); | ||
assert_eq!(config.read::<isize>(), Some(&-3)); | ||
config.write(3usize); | ||
assert_eq!(config.read::<usize>(), Some(&3)); | ||
} | ||
|
||
#[test] | ||
fn configuration_clone() { | ||
let mut config = Configuration::new(); | ||
config.write(42usize); | ||
config.write(String::from("foo")); | ||
|
||
let mut new_config = config.clone(); | ||
new_config.write(3usize); | ||
new_config.write(4u32); | ||
|
||
assert_eq!(config.read::<usize>(), Some(&42)); | ||
assert_eq!(config.read::<u32>(), None); | ||
assert_eq!(config.read::<String>(), Some(&"foo".into())); | ||
|
||
assert_eq!(new_config.read::<usize>(), Some(&3)); | ||
assert_eq!(new_config.read::<u32>(), Some(&4)); | ||
assert_eq!(new_config.read::<String>(), Some(&"foo".into())); | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.