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