Skip to content

Commit 7139364

Browse files
tirr-cyoshuawuyts
authored andcommitted
Per-endpoint configuration (#109)
* 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
1 parent 5f4cc3d commit 7139364

23 files changed

+619
-108
lines changed

examples/body_types.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,5 @@ fn main() {
5252
app.at("/echo/json").post(echo_json);
5353
app.at("/echo/form").post(echo_form);
5454

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

examples/catch_all.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,5 @@ fn main() {
1010
router.at("*").get(echo_path);
1111
});
1212

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

examples/computed_values.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,5 @@ fn main() {
3434
let mut app = tide::App::new(());
3535
app.at("/").get(hello_cookies);
3636

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

examples/configuration.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#![feature(async_await, futures_api)]
2+
3+
use futures::future::FutureObj;
4+
use tide::{head::Path, middleware::RequestContext, ExtractConfiguration, Response};
5+
6+
/// A type that represents how much value will be added by the `add` handler.
7+
#[derive(Clone, Debug, Default)]
8+
struct IncreaseBy(i32);
9+
10+
async fn add(
11+
Path(base): Path<i32>,
12+
// `ExtractConfiguration` will extract the configuration item of given type, and provide it as
13+
// `Option<T>`. If it is not set, the inner value will be `None`.
14+
ExtractConfiguration(amount): ExtractConfiguration<IncreaseBy>,
15+
) -> String {
16+
let IncreaseBy(amount) = amount.unwrap_or_default();
17+
format!("{} plus {} is {}", base, amount, base + amount)
18+
}
19+
20+
fn debug_store(ctx: RequestContext<()>) -> FutureObj<Response> {
21+
println!("{:#?}", ctx.store());
22+
ctx.next()
23+
}
24+
25+
fn main() {
26+
let mut app = tide::App::new(());
27+
// `App::config` sets the default configuration of the app (that is, a top-level router).
28+
app.config(IncreaseBy(1));
29+
app.middleware(debug_store);
30+
app.at("add_one/{}").get(add); // `IncreaseBy` is set to 1
31+
app.at("add_two/{}").get(add).config(IncreaseBy(2)); // `IncreaseBy` is overridden to 2
32+
33+
app.serve();
34+
}

examples/default_handler.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,5 @@ fn main() {
99

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

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

examples/default_headers.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,5 @@ fn main() {
1313

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

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

examples/graphql.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,5 @@ fn main() {
7070

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

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

examples/hello.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,5 @@ fn main() {
44
let mut app = tide::App::new(());
55
app.at("/").get(async || "Hello, world!");
66

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

examples/messages.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,5 @@ fn main() {
8181
app.at("/message/{}").get(get_message);
8282
app.at("/message/{}").post(set_message);
8383

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

examples/multipart-form/main.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,7 @@ fn main() {
6565

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

68-
let address = "127.0.0.1:8000".to_owned();
69-
println!("Server is listening on http://{}", address);
70-
app.serve(address);
68+
app.serve();
7169
}
7270

7371
// Test with:

examples/named_path.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,5 @@ fn main() {
2525
let mut app = tide::App::new(());
2626
app.at("add_two/{num}").get(add_two);
2727

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

examples/simple_nested_router.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,5 @@ fn main() {
3434
let mut app = tide::App::new(());
3535
app.at("add_two").nest(build_add_two);
3636

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

src/app.rs

Lines changed: 56 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,20 @@ use futures::{
55
};
66
use hyper::service::Service;
77
use std::{
8+
any::Any,
9+
fmt::Debug,
810
ops::{Deref, DerefMut},
911
sync::Arc,
1012
};
1113

1214
use crate::{
1315
body::Body,
16+
configuration::{Configuration, Store},
1417
endpoint::BoxedEndpoint,
1518
endpoint::Endpoint,
1619
extract::Extract,
1720
middleware::{logger::RootLogger, RequestContext},
18-
router::{Resource, RouteResult, Router},
21+
router::{EndpointData, Resource, RouteResult, Router},
1922
Middleware, Request, Response, RouteMatch,
2023
};
2124

@@ -35,7 +38,7 @@ use crate::{
3538
///
3639
/// let mut app = tide::App::new(());
3740
/// app.at("/hello").get(async || "Hello, world!");
38-
/// app.serve("127.0.0.1:7878")
41+
/// app.serve()
3942
/// ```
4043
///
4144
/// `App` state can be modeled with an underlying `Data` handle for a cloneable type `T`. Endpoints
@@ -74,7 +77,7 @@ use crate::{
7477
/// fn main() {
7578
/// let mut app = tide::App::new(Database::new());
7679
/// app.at("/messages/insert").post(insert);
77-
/// app.serve("127.0.0.1:7878")
80+
/// app.serve()
7881
/// }
7982
/// ```
8083
///
@@ -84,7 +87,7 @@ use crate::{
8487
pub struct App<Data> {
8588
data: Data,
8689
router: Router<Data>,
87-
default_handler: BoxedEndpoint<Data>,
90+
default_handler: EndpointData<Data>,
8891
}
8992

9093
impl<Data: Clone + Send + Sync + 'static> App<Data> {
@@ -94,14 +97,25 @@ impl<Data: Clone + Send + Sync + 'static> App<Data> {
9497
let mut app = App {
9598
data,
9699
router: Router::new(),
97-
default_handler: BoxedEndpoint::new(async || http::status::StatusCode::NOT_FOUND),
100+
default_handler: EndpointData {
101+
endpoint: BoxedEndpoint::new(async || http::status::StatusCode::NOT_FOUND),
102+
store: Store::new(),
103+
},
98104
};
99105

100106
// Add RootLogger as a default middleware
101107
app.middleware(logger);
108+
app.setup_configuration();
109+
102110
app
103111
}
104112

113+
// Add default configuration
114+
fn setup_configuration(&mut self) {
115+
let config = Configuration::build().finalize();
116+
self.config(config);
117+
}
118+
105119
/// Get the top-level router.
106120
pub fn router(&mut self) -> &mut Router<Data> {
107121
&mut self.router
@@ -114,9 +128,16 @@ impl<Data: Clone + Send + Sync + 'static> App<Data> {
114128
}
115129

116130
/// Set the default handler for the app, a fallback function when there is no match to the route requested
117-
pub fn default_handler<T: Endpoint<Data, U>, U>(&mut self, handler: T) -> &mut Self {
118-
self.default_handler = BoxedEndpoint::new(handler);
119-
self
131+
pub fn default_handler<T: Endpoint<Data, U>, U>(
132+
&mut self,
133+
handler: T,
134+
) -> &mut EndpointData<Data> {
135+
let endpoint = EndpointData {
136+
endpoint: BoxedEndpoint::new(handler),
137+
store: self.router.store_base.clone(),
138+
};
139+
self.default_handler = endpoint;
140+
&mut self.default_handler
120141
}
121142

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

129-
fn into_server(self) -> Server<Data> {
150+
/// Add a default configuration `item` for the whole app.
151+
pub fn config<T: Any + Debug + Clone + Send + Sync>(&mut self, item: T) -> &mut Self {
152+
self.router.config(item);
153+
self
154+
}
155+
156+
pub fn get_item<T: Any + Debug + Clone + Send + Sync>(&self) -> Option<&T> {
157+
self.router.get_item()
158+
}
159+
160+
fn into_server(mut self) -> Server<Data> {
161+
self.router.apply_default_config();
130162
Server {
131163
data: self.data,
132164
router: Arc::new(self.router),
@@ -137,12 +169,17 @@ impl<Data: Clone + Send + Sync + 'static> App<Data> {
137169
/// Start serving the app at the given address.
138170
///
139171
/// Blocks the calling thread indefinitely.
140-
pub fn serve<A: std::net::ToSocketAddrs>(self, addr: A) {
172+
pub fn serve(self) {
173+
let configuration = self.get_item::<Configuration>().unwrap();
174+
let addr = format!("{}:{}", configuration.address, configuration.port)
175+
.parse::<std::net::SocketAddr>()
176+
.unwrap();
177+
178+
println!("Server is listening on: http://{}", addr);
179+
141180
let server: Server<Data> = self.into_server();
142181

143182
// TODO: be more robust
144-
let addr = addr.to_socket_addrs().unwrap().next().unwrap();
145-
146183
let server = hyper::Server::bind(&addr)
147184
.serve(move || {
148185
let res: Result<_, std::io::Error> = Ok(server.clone());
@@ -162,7 +199,7 @@ impl<Data: Clone + Send + Sync + 'static> App<Data> {
162199
struct Server<Data> {
163200
data: Data,
164201
router: Arc<Router<Data>>,
165-
default_handler: Arc<BoxedEndpoint<Data>>,
202+
default_handler: Arc<EndpointData<Data>>,
166203
}
167204

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

224261
impl<T: Clone + Send + 'static> Extract<T> for AppData<T> {
225262
type Fut = future::Ready<Result<Self, Response>>;
226-
fn extract(data: &mut T, req: &mut Request, params: &Option<RouteMatch<'_>>) -> Self::Fut {
263+
fn extract(
264+
data: &mut T,
265+
req: &mut Request,
266+
params: &Option<RouteMatch<'_>>,
267+
store: &Store,
268+
) -> Self::Fut {
227269
future::ok(AppData(data.clone()))
228270
}
229271
}

0 commit comments

Comments
 (0)