mirror of https://github.com/rwf2/Rocket.git
Initial commit. Checking for method and path arguments in route. Not storing the info anywhere yet.
This commit is contained in:
commit
967fcd26b2
|
@ -0,0 +1,15 @@
|
||||||
|
# Compiled files
|
||||||
|
*.o
|
||||||
|
*.so
|
||||||
|
*.rlib
|
||||||
|
*.dll
|
||||||
|
|
||||||
|
# Executables
|
||||||
|
*.exe
|
||||||
|
|
||||||
|
# Generated by Cargo
|
||||||
|
/target/
|
||||||
|
|
||||||
|
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
||||||
|
# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
|
||||||
|
Cargo.lock
|
|
@ -0,0 +1,8 @@
|
||||||
|
[package]
|
||||||
|
name = "rocket"
|
||||||
|
version = "0.0.1"
|
||||||
|
authors = ["Sergio Benitez <sb@sergio.bz>"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
hyper = "> 0.7.0"
|
||||||
|
rocket_macros = { path = "macros" }
|
|
@ -0,0 +1,194 @@
|
||||||
|
# Rust Web Framework
|
||||||
|
|
||||||
|
I really want a nice, easy to use, safe, and stupid fast web framework for
|
||||||
|
Rust. I don't want a monolithic Rails like thing. I'd much rather have
|
||||||
|
something that looks like Bottle. That is, use decorators to declare routes.
|
||||||
|
|
||||||
|
Nickel.rs looks kinda nice though (see example 2 below - that looks like, but
|
||||||
|
isn't Nickel).
|
||||||
|
|
||||||
|
Here's what the simplest program might look like with this framework:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[route("/")]
|
||||||
|
fn home() -> Response {
|
||||||
|
Response::string("Hello, world!")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
RustWebFramework::run("localhost");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Alternatively...
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn home() -> Response {
|
||||||
|
Response::string("Hello, world!")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
route! {
|
||||||
|
get '/' => home,
|
||||||
|
}
|
||||||
|
|
||||||
|
RustWebFramework::run("localhost");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Arguments
|
||||||
|
|
||||||
|
Here's what a route that takes arguments might look like:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[route("/<page>")]
|
||||||
|
fn home(page: &str) -> Response {
|
||||||
|
Response::string(page)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The really neat thing here is that the `route` macro will typecheck the
|
||||||
|
function signature. The signature should also have a return type of `Response`
|
||||||
|
(or whatever the response type ends up being) and take a number of equivalent
|
||||||
|
to those present in the route. The type of the arguments can be any `T` that
|
||||||
|
implements `From<&str>`. The conversion will be done automatically by the route
|
||||||
|
handler. As such, the following will work as expected:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[route("/users/<id>")]
|
||||||
|
fn home(id: isize) -> Response {
|
||||||
|
let response_string = format!("User ID: {}", id);
|
||||||
|
Response::string(response_string)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If the conversion fails, the router should 1) print out a debug error message
|
||||||
|
and return some user-set no-route-exists things, and 2) allow the programmer to
|
||||||
|
catch the failure if needed. I'm not quite sure what the best way to allow 2)
|
||||||
|
is at the moment. Here are a couple of ideas:
|
||||||
|
|
||||||
|
1. Add an `else` parameter to the `route` macro that will take in the name
|
||||||
|
of a function to call with the raw string (and more) if the routing
|
||||||
|
fails:
|
||||||
|
|
||||||
|
#[route("/users/<id>", else = home_failed)]
|
||||||
|
fn home(id: isize) -> Response { ... }
|
||||||
|
fn home_failed(route: &str) -> Response { ... }
|
||||||
|
|
||||||
|
2. Allow the parameter type to be `Result<T>`. Then the route is always
|
||||||
|
called and the user has to check if the conversion was successful or not.
|
||||||
|
|
||||||
|
3. Pass it off as an error type to another handler.
|
||||||
|
|
||||||
|
Open questions here:
|
||||||
|
|
||||||
|
1. What syntax should be used to match a path component to a regular
|
||||||
|
expression? If for some parameter, call it `<name>`, of type `&str`, we
|
||||||
|
want to constrain matches to the route to `name`s that match some
|
||||||
|
regular expression, say `[a-z]+`, how do we specify that? Bottle does:
|
||||||
|
|
||||||
|
<name:re:[a-z]+>
|
||||||
|
|
||||||
|
We can probably just do:
|
||||||
|
|
||||||
|
<name: [a-z]+>
|
||||||
|
|
||||||
|
|
||||||
|
## Methods
|
||||||
|
|
||||||
|
A different HTTP method can be specified with the `method` `route` argument:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[route(method = POST, "/users")]
|
||||||
|
fn add_user(name: &str, age: isize) -> Response { ... }
|
||||||
|
```
|
||||||
|
|
||||||
|
Or, more succinctly:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[POST("/users")]
|
||||||
|
fn add_user(name: &str, age: isize) -> Response { ... }
|
||||||
|
```
|
||||||
|
|
||||||
|
## Route Priority
|
||||||
|
|
||||||
|
Do we allow two routes to possibly match a single path? Can we even determine
|
||||||
|
that no two paths conflict given regular expressions? Answer: Yes
|
||||||
|
(http://arstechnica.com/civis/viewtopic.php?f=20&t=472178). And if so, which
|
||||||
|
route gets priority? An idea is to add a `priority` parameter to the `route`
|
||||||
|
macro:
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[GET("/[name: [a-zA-Z]+", priority = 0)]
|
||||||
|
#[GET("/[name: [a-z]+", priority = 1)]
|
||||||
|
```
|
||||||
|
|
||||||
|
The first route allows lower and uppercase letter, while the second route only
|
||||||
|
allows lowercase letters. In the case that the entire route has lowercase
|
||||||
|
letters, the route with the higher priority (1, here) gets called, i.e., the
|
||||||
|
second one.
|
||||||
|
|
||||||
|
## Error Pages
|
||||||
|
|
||||||
|
There's a route for error pages, too:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[route(method = ERROR, status = 404)]
|
||||||
|
fn page_not_found(...not sure what goes here yet...) -> Response { .. }
|
||||||
|
```
|
||||||
|
|
||||||
|
Or, more succinctly:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[error(404)]
|
||||||
|
fn page_not_found(...not sure what goes here yet...) -> Response { .. }
|
||||||
|
```
|
||||||
|
|
||||||
|
## Open Questions
|
||||||
|
|
||||||
|
1. How is HTTP data handled? (IE, interpret `Content-Type`s)
|
||||||
|
- Form Data
|
||||||
|
- JSON: Would be nice to automatically convert to structs.
|
||||||
|
|
||||||
|
2. What about Query Params?
|
||||||
|
|
||||||
|
3. How are cookies handled?
|
||||||
|
|
||||||
|
Presumably you would set them via `Response` and get them via...?
|
||||||
|
|
||||||
|
4. Easy support for (but don't bundle it in...) templating would be nice.
|
||||||
|
Bottle lets you do:
|
||||||
|
|
||||||
|
#[view("template_name")]
|
||||||
|
fn hello(...) -> HashMap { .. }
|
||||||
|
|
||||||
|
and automatically instantiates the template `template_name` with the
|
||||||
|
parameters from the HashMap.
|
||||||
|
|
||||||
|
5. Autoreloading. Maybe use the unix-y reloading thing. Maybe not.
|
||||||
|
|
||||||
|
6. Plugins? Would be nice to easily extend routes.
|
||||||
|
|
||||||
|
7. Pre-post hooks/filters?
|
||||||
|
|
||||||
|
8. Caching?
|
||||||
|
|
||||||
|
9. Session support?
|
||||||
|
|
||||||
|
This is basically a server-side local store identified via an ID in a
|
||||||
|
cookie.
|
||||||
|
|
||||||
|
http://entrproject.org/
|
||||||
|
|
||||||
|
10. Environment support? (debug vs. production vs. test, etc.)
|
||||||
|
|
||||||
|
11. Model validation?
|
||||||
|
|
||||||
|
12. Internationalization?
|
||||||
|
|
||||||
|
13. Be faster than https://github.com/julienschmidt/httprouter.
|
||||||
|
|
||||||
|
For 2, 3: the obvious solution is to have a `Request` object with that
|
||||||
|
information. Do we need that, though? Is there something better?
|
|
@ -0,0 +1,12 @@
|
||||||
|
[package]
|
||||||
|
name = "rocket_macros"
|
||||||
|
version = "0.0.1"
|
||||||
|
authors = ["Sergio Benitez <sb@sergio.bz>"]
|
||||||
|
description = "Core Rocket Macros"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
hyper = "> 0.0.0"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "rocket_macros"
|
||||||
|
plugin = true
|
|
@ -0,0 +1,134 @@
|
||||||
|
#![crate_type = "dylib"]
|
||||||
|
#![feature(plugin_registrar, rustc_private)]
|
||||||
|
|
||||||
|
extern crate syntax;
|
||||||
|
extern crate rustc;
|
||||||
|
extern crate rustc_plugin;
|
||||||
|
extern crate hyper;
|
||||||
|
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use rustc_plugin::Registry;
|
||||||
|
use syntax::parse::token::{intern};
|
||||||
|
use syntax::ext::base::SyntaxExtension;
|
||||||
|
use std::default::Default;
|
||||||
|
|
||||||
|
use syntax::codemap::Span;
|
||||||
|
use syntax::ast::{Item, ItemKind, MetaItem, MetaItemKind, FnDecl, LitKind};
|
||||||
|
use syntax::ext::base::{Annotatable, ExtCtxt};
|
||||||
|
use syntax::ptr::P;
|
||||||
|
|
||||||
|
use hyper::method::Method;
|
||||||
|
|
||||||
|
fn bad_item_fatal(ecx: &mut ExtCtxt, dec_sp: Span, item_sp: Span) -> ! {
|
||||||
|
ecx.span_err(dec_sp, "This decorator cannot be used on non-functions...");
|
||||||
|
ecx.span_fatal(item_sp, "...but an attempt to use it on the item below was made.")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bad_method_err(ecx: &mut ExtCtxt, dec_sp: Span, method: &str) {
|
||||||
|
let message = format!("`{}` is not a valid method. Valid methods are: \
|
||||||
|
[GET, POST, PUT, DELETE, HEAD, PATCH]", method);
|
||||||
|
ecx.span_err(dec_sp, message.as_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
struct RouteParams {
|
||||||
|
method: Method,
|
||||||
|
path: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn demo_decorator(ecx: &mut ExtCtxt, sp: Span, meta_item: &MetaItem,
|
||||||
|
annotated: &Annotatable, _push: &mut FnMut(Annotatable)) {
|
||||||
|
// Word: #[demo]
|
||||||
|
// List: #[demo(one, two, ..)] or #[demo(one = "1", ...)] or mix both
|
||||||
|
// NameValue: #[demo = "1"]
|
||||||
|
let params: &Vec<P<MetaItem>> = match meta_item.node {
|
||||||
|
MetaItemKind::List(_, ref params) => params,
|
||||||
|
// Would almost certainly be better to use "DummyResult" here.
|
||||||
|
_ => ecx.span_fatal(meta_item.span,
|
||||||
|
"incorrect use of macro. correct form is: #[demo(...)]"),
|
||||||
|
};
|
||||||
|
|
||||||
|
if params.len() < 2 {
|
||||||
|
ecx.span_fatal(meta_item.span, "Bad invocation. Need >= 2 arguments.");
|
||||||
|
}
|
||||||
|
|
||||||
|
let (method_param, kv_params) = params.split_first().unwrap();
|
||||||
|
let method = if let MetaItemKind::Word(ref word) = method_param.node {
|
||||||
|
let method = Method::from_str(word).unwrap_or_else(|e| {
|
||||||
|
Method::Extension(String::from(&**word))
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Method::Extension(ref name) = method {
|
||||||
|
bad_method_err(ecx, meta_item.span, name.as_str());
|
||||||
|
Method::Get
|
||||||
|
} else {
|
||||||
|
method
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Method::Get
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut route_params: RouteParams = RouteParams {
|
||||||
|
method: method,
|
||||||
|
path: String::new()
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut found_path = false;
|
||||||
|
for param in kv_params {
|
||||||
|
if let MetaItemKind::NameValue(ref name, ref value) = param.node {
|
||||||
|
match &**name {
|
||||||
|
"path" => {
|
||||||
|
found_path = true;
|
||||||
|
if let LitKind::Str(ref string, _) = value.node {
|
||||||
|
route_params.path = String::from(&**string);
|
||||||
|
} else {
|
||||||
|
ecx.span_err(param.span, "Path value must be string.");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
ecx.span_err(param.span, "Unrecognized parameter.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
ecx.span_err(param.span, "Invalid parameter. Must be key = value.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found_path {
|
||||||
|
ecx.span_err(meta_item.span, "`path` argument is missing.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// for param in params {
|
||||||
|
// if let MetaItemKind::Word(ref word) = param.node {
|
||||||
|
// if hyper::method::Method::from_str(word).is_ok() {
|
||||||
|
// println!("METHOD! {}", word);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// println!("WORD Param: {:?}", param);
|
||||||
|
// } else {
|
||||||
|
// println!("NOT word Param: {:?}", param);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// `annotated` is the AST object for the annotated item.
|
||||||
|
let item: &P<Item> = match annotated {
|
||||||
|
&Annotatable::Item(ref item) => item,
|
||||||
|
&Annotatable::TraitItem(ref item) => bad_item_fatal(ecx, sp, item.span),
|
||||||
|
&Annotatable::ImplItem(ref item) => bad_item_fatal(ecx, sp, item.span)
|
||||||
|
};
|
||||||
|
|
||||||
|
let fn_decl: &P<FnDecl> = match item.node {
|
||||||
|
ItemKind::Fn(ref decl, _, _, _, _, _) => decl,
|
||||||
|
_ => bad_item_fatal(ecx, sp, item.span)
|
||||||
|
};
|
||||||
|
|
||||||
|
println!("Function arguments: {:?}", fn_decl.inputs);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[plugin_registrar]
|
||||||
|
pub fn plugin_registrar(reg: &mut Registry) {
|
||||||
|
// reg.register_macro("rn", expand_rn);
|
||||||
|
reg.register_syntax_extension(intern("route"),
|
||||||
|
SyntaxExtension::MultiDecorator(Box::new(demo_decorator)));
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
#![feature(plugin)]
|
||||||
|
#![plugin(rocket_macros)]
|
||||||
|
|
||||||
|
#[route(POST, path = "/")]
|
||||||
|
fn function(_x: usize, _y: isize) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#[route(GET, path = "/")]
|
||||||
|
fn main() {
|
||||||
|
println!("Hello, world!");
|
||||||
|
function(1, 2);
|
||||||
|
}
|
Loading…
Reference in New Issue