Skip to content

Commit

Permalink
Per-endpoint configuration (#109)
Browse files Browse the repository at this point in the history
* Add configuration types

* Expose configuration to endpoints

* Make configurations actually configurable

* Use typemap approach for configuration

* Add example for configuration

* Change module organization of configuration

* Add docs for configuration module

* Add tests for Configuration, fix existing tests

* Run rustfmt

* Change how the configuration is nested

* Add tests for Router with configuration

* Add comments to configuration example

* Change the name of `RequestConext::get_config` to get_config_item

* Rename Configuration to Store and move it to configuration module.

* This is because the Confiugration type is a App global sharable
TypeMap which is avaiable across both middleware and endpoints.
Therefore, this can be used to store arbitrary types and not only
configuartion.
* Tis commit renames all occurrances of Configuration to Store and
updates the parameters and struct field names appropriately.

* Add default configuration and configuration builder types

* Have simple hook into the App by the way of the `setup_configuration`
method. This is pretty basic rightn now, but can be the point of entry
for hooking in custom configuration.

* Simple useage of app configuration internally

* Use address from confiugration when starting up server

* Fix tests

* Adress review comments.

* Add more documentation for Configuration

* Address clippy lints

* Make naming consistent

* Change name of Store tests

* Add Debug impl for Store

* Add configuration debug example
  • Loading branch information
tirr-c authored and yoshuawuyts committed Jan 17, 2019
1 parent 5f4cc3d commit 7139364
Show file tree
Hide file tree
Showing 23 changed files with 619 additions and 108 deletions.
4 changes: 1 addition & 3 deletions examples/body_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,5 @@ fn main() {
app.at("/echo/json").post(echo_json);
app.at("/echo/form").post(echo_form);

let address = "127.0.0.1:8000".to_owned();
println!("Server is listening on http://{}", address);
app.serve(address);
app.serve();
}
4 changes: 1 addition & 3 deletions examples/catch_all.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,5 @@ fn main() {
router.at("*").get(echo_path);
});

let address = "127.0.0.1:8000".to_owned();
println!("Server is listening on http://{}", address);
app.serve(address);
app.serve();
}
4 changes: 1 addition & 3 deletions examples/computed_values.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,5 @@ fn main() {
let mut app = tide::App::new(());
app.at("/").get(hello_cookies);

let address = "127.0.0.1:8000".to_owned();
println!("Server is listening on http://{}", address);
app.serve(address);
app.serve();
}
34 changes: 34 additions & 0 deletions examples/configuration.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#![feature(async_await, futures_api)]

use futures::future::FutureObj;
use tide::{head::Path, middleware::RequestContext, ExtractConfiguration, Response};

/// A type that represents how much value will be added by the `add` handler.
#[derive(Clone, Debug, 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>,
) -> String {
let IncreaseBy(amount) = amount.unwrap_or_default();
format!("{} plus {} is {}", base, amount, base + amount)
}

fn debug_store(ctx: RequestContext<()>) -> FutureObj<Response> {
println!("{:#?}", ctx.store());
ctx.next()
}

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.middleware(debug_store);
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

app.serve();
}
4 changes: 1 addition & 3 deletions examples/default_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,5 @@ fn main() {

app.default_handler(async || \\_(ツ)_/¯".with_status(StatusCode::NOT_FOUND));

let address = "127.0.0.1:8000".to_owned();
println!("Server is listening on http://{}", address);
app.serve(address)
app.serve()
}
4 changes: 1 addition & 3 deletions examples/default_headers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,5 @@ fn main() {

app.at("/").get(async || "Hello, world!");

let address = "127.0.0.1:8000".to_owned();
println!("Server is listening on http://{}", address);
app.serve(address);
app.serve();
}
4 changes: 1 addition & 3 deletions examples/graphql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,5 @@ fn main() {

app.at("/graphql").post(handle_graphql);

let address = "127.0.0.1:8000".to_owned();
println!("Server is listening on http://{}", address);
app.serve(address);
app.serve();
}
4 changes: 1 addition & 3 deletions examples/hello.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,5 @@ fn main() {
let mut app = tide::App::new(());
app.at("/").get(async || "Hello, world!");

let address = "127.0.0.1:8000".to_owned();
println!("Server is listening on http://{}", address);
app.serve(address);
app.serve();
}
4 changes: 1 addition & 3 deletions examples/messages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,5 @@ fn main() {
app.at("/message/{}").get(get_message);
app.at("/message/{}").post(set_message);

let address = "127.0.0.1:8000".to_owned();
println!("Server is listening on http://{}", address);
app.serve(address);
app.serve();
}
4 changes: 1 addition & 3 deletions examples/multipart-form/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,7 @@ fn main() {

app.at("/upload_file").post(upload_file);

let address = "127.0.0.1:8000".to_owned();
println!("Server is listening on http://{}", address);
app.serve(address);
app.serve();
}

// Test with:
Expand Down
4 changes: 1 addition & 3 deletions examples/named_path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,5 @@ fn main() {
let mut app = tide::App::new(());
app.at("add_two/{num}").get(add_two);

let address = "127.0.0.1:8000".to_owned();
println!("Server is listening on http://{}", address);
app.serve(address);
app.serve();
}
4 changes: 1 addition & 3 deletions examples/simple_nested_router.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,5 @@ fn main() {
let mut app = tide::App::new(());
app.at("add_two").nest(build_add_two);

let address = "127.0.0.1:8000".to_owned();
println!("Server is listening on http://{}", address);
app.serve(address);
app.serve();
}
70 changes: 56 additions & 14 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,20 @@ use futures::{
};
use hyper::service::Service;
use std::{
any::Any,
fmt::Debug,
ops::{Deref, DerefMut},
sync::Arc,
};

use crate::{
body::Body,
configuration::{Configuration, Store},
endpoint::BoxedEndpoint,
endpoint::Endpoint,
extract::Extract,
middleware::{logger::RootLogger, RequestContext},
router::{Resource, RouteResult, Router},
router::{EndpointData, Resource, RouteResult, Router},
Middleware, Request, Response, RouteMatch,
};

Expand All @@ -35,7 +38,7 @@ use crate::{
///
/// let mut app = tide::App::new(());
/// app.at("/hello").get(async || "Hello, world!");
/// app.serve("127.0.0.1:7878")
/// app.serve()
/// ```
///
/// `App` state can be modeled with an underlying `Data` handle for a cloneable type `T`. Endpoints
Expand Down Expand Up @@ -74,7 +77,7 @@ use crate::{
/// fn main() {
/// let mut app = tide::App::new(Database::new());
/// app.at("/messages/insert").post(insert);
/// app.serve("127.0.0.1:7878")
/// app.serve()
/// }
/// ```
///
Expand All @@ -84,7 +87,7 @@ use crate::{
pub struct App<Data> {
data: Data,
router: Router<Data>,
default_handler: BoxedEndpoint<Data>,
default_handler: EndpointData<Data>,
}

impl<Data: Clone + Send + Sync + 'static> App<Data> {
Expand All @@ -94,14 +97,25 @@ impl<Data: Clone + Send + Sync + 'static> App<Data> {
let mut app = App {
data,
router: Router::new(),
default_handler: BoxedEndpoint::new(async || http::status::StatusCode::NOT_FOUND),
default_handler: EndpointData {
endpoint: BoxedEndpoint::new(async || http::status::StatusCode::NOT_FOUND),
store: Store::new(),
},
};

// Add RootLogger as a default middleware
app.middleware(logger);
app.setup_configuration();

app
}

// Add default configuration
fn setup_configuration(&mut self) {
let config = Configuration::build().finalize();
self.config(config);
}

/// Get the top-level router.
pub fn router(&mut self) -> &mut Router<Data> {
&mut self.router
Expand All @@ -114,9 +128,16 @@ impl<Data: Clone + Send + Sync + 'static> App<Data> {
}

/// Set the default handler for the app, a fallback function when there is no match to the route requested
pub fn default_handler<T: Endpoint<Data, U>, U>(&mut self, handler: T) -> &mut Self {
self.default_handler = BoxedEndpoint::new(handler);
self
pub fn default_handler<T: Endpoint<Data, U>, U>(
&mut self,
handler: T,
) -> &mut EndpointData<Data> {
let endpoint = EndpointData {
endpoint: BoxedEndpoint::new(handler),
store: self.router.store_base.clone(),
};
self.default_handler = endpoint;
&mut self.default_handler
}

/// Apply `middleware` to the whole app. Note that the order of nesting subrouters and applying
Expand All @@ -126,7 +147,18 @@ impl<Data: Clone + Send + Sync + 'static> App<Data> {
self
}

fn into_server(self) -> Server<Data> {
/// Add a default configuration `item` for the whole app.
pub fn config<T: Any + Debug + Clone + Send + Sync>(&mut self, item: T) -> &mut Self {
self.router.config(item);
self
}

pub fn get_item<T: Any + Debug + Clone + Send + Sync>(&self) -> Option<&T> {
self.router.get_item()
}

fn into_server(mut self) -> Server<Data> {
self.router.apply_default_config();
Server {
data: self.data,
router: Arc::new(self.router),
Expand All @@ -137,12 +169,17 @@ impl<Data: Clone + Send + Sync + 'static> App<Data> {
/// Start serving the app at the given address.
///
/// Blocks the calling thread indefinitely.
pub fn serve<A: std::net::ToSocketAddrs>(self, addr: A) {
pub fn serve(self) {
let configuration = self.get_item::<Configuration>().unwrap();
let addr = format!("{}:{}", configuration.address, configuration.port)
.parse::<std::net::SocketAddr>()
.unwrap();

println!("Server is listening on: http://{}", addr);

let server: Server<Data> = self.into_server();

// TODO: be more robust
let addr = addr.to_socket_addrs().unwrap().next().unwrap();

let server = hyper::Server::bind(&addr)
.serve(move || {
let res: Result<_, std::io::Error> = Ok(server.clone());
Expand All @@ -162,7 +199,7 @@ impl<Data: Clone + Send + Sync + 'static> App<Data> {
struct Server<Data> {
data: Data,
router: Arc<Router<Data>>,
default_handler: Arc<BoxedEndpoint<Data>>,
default_handler: Arc<EndpointData<Data>>,
}

impl<Data: Clone + Send + Sync + 'static> Service for Server<Data> {
Expand Down Expand Up @@ -223,7 +260,12 @@ impl<T> DerefMut for AppData<T> {

impl<T: Clone + Send + 'static> Extract<T> for AppData<T> {
type Fut = future::Ready<Result<Self, Response>>;
fn extract(data: &mut T, req: &mut Request, params: &Option<RouteMatch<'_>>) -> Self::Fut {
fn extract(
data: &mut T,
req: &mut Request,
params: &Option<RouteMatch<'_>>,
store: &Store,
) -> Self::Fut {
future::ok(AppData(data.clone()))
}
}
Loading

0 comments on commit 7139364

Please sign in to comment.