Attempt nicer url segment parsing.

This commit is contained in:
Rasmus Kaj 2016-07-22 00:20:38 +02:00
parent 3767319ff6
commit 4b5940fa48
2 changed files with 335 additions and 223 deletions

View File

@ -19,11 +19,13 @@ extern crate nickel_diesel;
#[macro_use] #[macro_use]
extern crate diesel; extern crate diesel;
extern crate r2d2_diesel; extern crate r2d2_diesel;
extern crate dotenv;
use nickel_diesel::{DieselMiddleware, DieselRequestExtensions}; use nickel_diesel::{DieselMiddleware, DieselRequestExtensions};
use r2d2::NopErrorHandler; use r2d2::NopErrorHandler;
use chrono::Duration as ChDuration; use chrono::Duration as ChDuration;
use chrono::Datelike; use chrono::Datelike;
use dotenv::dotenv;
use hyper::header::{Expires, HttpDate}; use hyper::header::{Expires, HttpDate};
use nickel::{FormBody, HttpRouter, MediaType, MiddlewareResult, Nickel, use nickel::{FormBody, HttpRouter, MediaType, MiddlewareResult, Nickel,
Request, Response, StaticFilesHandler}; Request, Response, StaticFilesHandler};
@ -50,6 +52,9 @@ use requestloggermiddleware::RequestLoggerMiddleware;
mod photosdirmiddleware; mod photosdirmiddleware;
use photosdirmiddleware::{PhotosDirMiddleware, PhotosDirRequestExtensions}; use photosdirmiddleware::{PhotosDirMiddleware, PhotosDirRequestExtensions};
#[macro_use]
mod nickelext;
use nickelext::FromSlug;
macro_rules! render { macro_rules! render {
($res:expr, $template:expr, { $($param:ident : $ptype:ty = $value:expr),* }) ($res:expr, $template:expr, { $($param:ident : $ptype:ty = $value:expr),* })
@ -105,7 +110,9 @@ fn monthname(n: u8) -> &'static str {
} }
} }
fn main() { fn main() {
dotenv().ok();
env_logger::init().unwrap(); env_logger::init().unwrap();
info!("Initalized logger"); info!("Initalized logger");
@ -121,22 +128,22 @@ fn main() {
server.utilize(dm); server.utilize(dm);
server.utilize(PhotosDirMiddleware::new(photos_dir())); server.utilize(PhotosDirMiddleware::new(photos_dir()));
server.get("/login", login); wrap2!(server.get /login, login);
server.post("/login", do_login); wrap2!(server.post /login, do_login);
server.get("/logout", logout); wrap2!(server.get /logout, logout);
server.get("/", all_years); server.get("/", all_years);
server.get("/img/:id/:size", show_image); wrap2!(server.get /img/:id/:size, show_image);
server.get("/tag/", tag_all); wrap2!(server.get /tag/, tag_all);
server.get("/tag/:tag", tag_one); wrap2!(server.get /tag/:tag, tag_one);
server.get("/place/", place_all); wrap2!(server.get /place/, place_all);
server.get("/place/:slug", place_one); wrap2!(server.get /place/:slug, place_one);
server.get("/person/", person_all); wrap2!(server.get /person/, person_all);
server.get("/person/:slug", person_one); wrap2!(server.get /person/:slug, person_one);
server.get("/details/:id", photo_details); wrap2!(server.get /details/:id, photo_details);
server.get("/:year/", months_in_year); wrap2!(server.get /:year/, months_in_year);
server.get("/:year/:month/", days_in_month); wrap2!(server.get /:year/:month/, days_in_month);
server.get("/:year/:month/:day", all_for_day); wrap2!(server.get /:year/:month/:day/, all_for_day);
server.get("/thisday", on_this_day); wrap2!(server.get /thisday, on_this_day);
server.listen(&*env_or("RPHOTOS_LISTEN", "127.0.0.1:6767")); server.listen(&*env_or("RPHOTOS_LISTEN", "127.0.0.1:6767"));
} }
@ -181,33 +188,58 @@ fn logout<'mw>(_req: &mut Request,
res.redirect("/") res.redirect("/")
} }
fn show_image<'mw>(req: &mut Request, enum SizeTag {
mut res: Response<'mw>) Small,
Medium,
Large,
}
impl SizeTag {
fn px(&self) -> u32 {
match *self {
SizeTag::Small => 200,
SizeTag::Medium => 800,
SizeTag::Large => 1200,
}
}
}
impl FromSlug for SizeTag {
fn parse_slug(slug: &str) -> Option<Self> {
match slug {
"s" => Some(SizeTag::Small),
"m" => Some(SizeTag::Medium),
"l" => Some(SizeTag::Large),
_ => None
}
}
}
fn opt<T: FromSlug>(req: &Request, name: &str) -> Option<T> {
req.param(name).and_then(T::parse_slug)
}
fn show_image<'mw>(req: &Request,
mut res: Response<'mw>,
the_id: i32,
size: SizeTag)
-> MiddlewareResult<'mw> { -> MiddlewareResult<'mw> {
if let Ok(the_id) = req.param("id").unwrap().parse::<i32>() { use rphotos::schema::photos::dsl::*;
use rphotos::schema::photos::dsl::*; let connection = req.db_conn();
let connection = req.db_conn(); let c: &PgConnection = &connection;
let c: &PgConnection = &connection; if let Ok(tphoto) = photos.find(the_id).first::<Photo>(c) {
if let Ok(tphoto) = photos.find(the_id).first::<Photo>(c) { if req.authorized_user().is_some() || tphoto.is_public() {
if req.authorized_user().is_some() || tphoto.is_public() { let size = size.px();
if let Some(size) = match req.param("size").unwrap() { match req.photos().get_scaled_image(tphoto, size, size) {
"s" => Some(200), Ok(buf) => {
"m" => Some(800), res.set(MediaType::Jpeg);
"l" => Some(1200), res.set(Expires(HttpDate(time::now() +
_ => None, Duration::days(14))));
} { return res.send(buf);
match req.photos().get_scaled_image(tphoto, size, size) { }
Ok(buf) => { Err(err) => {
res.set(MediaType::Jpeg); return res.error(StatusCode::InternalServerError,
res.set(Expires(HttpDate(time::now() + format!("{}", err));
Duration::days(14))));
return res.send(buf);
}
Err(err) => {
return res.error(StatusCode::InternalServerError,
format!("{}", err));
}
}
} }
} }
} }
@ -227,11 +259,12 @@ fn tag_all<'mw>(req: &mut Request,
tags: Vec<Tag> = tags.load(c).unwrap() tags: Vec<Tag> = tags.load(c).unwrap()
}); });
} }
fn tag_one<'mw>(req: &mut Request, fn tag_one<'mw>(req: &mut Request,
res: Response<'mw>) res: Response<'mw>,
tslug: String)
-> MiddlewareResult<'mw> { -> MiddlewareResult<'mw> {
use rphotos::schema::tags::dsl::*; use rphotos::schema::tags::dsl::*;
let tslug = req.param("tag").unwrap();
let connection = req.db_conn(); let connection = req.db_conn();
let c: &PgConnection = &connection; let c: &PgConnection = &connection;
if let Ok(tag) = tags.filter(slug.eq(tslug)).first::<Tag>(c) { if let Ok(tag) = tags.filter(slug.eq(tslug)).first::<Tag>(c) {
@ -266,10 +299,10 @@ fn place_all<'mw>(req: &mut Request,
} }
fn place_one<'mw>(req: &mut Request, fn place_one<'mw>(req: &mut Request,
res: Response<'mw>) res: Response<'mw>,
tslug: String)
-> MiddlewareResult<'mw> { -> MiddlewareResult<'mw> {
use rphotos::schema::places::dsl::*; use rphotos::schema::places::dsl::*;
let tslug = req.param("slug").unwrap();
let connection = req.db_conn(); let connection = req.db_conn();
let c: &PgConnection = &connection; let c: &PgConnection = &connection;
if let Ok(place) = places.filter(slug.eq(tslug)).first::<Place>(c) { if let Ok(place) = places.filter(slug.eq(tslug)).first::<Place>(c) {
@ -304,10 +337,10 @@ fn person_all<'mw>(req: &mut Request,
} }
fn person_one<'mw>(req: &mut Request, fn person_one<'mw>(req: &mut Request,
res: Response<'mw>) res: Response<'mw>,
tslug: String)
-> MiddlewareResult<'mw> { -> MiddlewareResult<'mw> {
use rphotos::schema::people::dsl::*; use rphotos::schema::people::dsl::*;
let tslug = req.param("slug").unwrap();
let connection = req.db_conn(); let connection = req.db_conn();
let c: &PgConnection = &connection; let c: &PgConnection = &connection;
if let Ok(person) = people.filter(slug.eq(tslug)).first::<Person>(c) { if let Ok(person) = people.filter(slug.eq(tslug)).first::<Person>(c) {
@ -330,59 +363,59 @@ fn person_one<'mw>(req: &mut Request,
} }
fn photo_details<'mw>(req: &mut Request, fn photo_details<'mw>(req: &mut Request,
res: Response<'mw>) res: Response<'mw>,
id: i32)
-> MiddlewareResult<'mw> { -> MiddlewareResult<'mw> {
if let Ok(the_id) = req.param("id").unwrap().parse::<i32>() { use rphotos::schema::photos::dsl::photos;
use rphotos::schema::photos::dsl::*; let connection = req.db_conn();
let connection = req.db_conn(); let c: &PgConnection = &connection;
let c: &PgConnection = &connection; if let Ok(tphoto) = photos.find(id).first::<Photo>(c) {
if let Ok(tphoto) = photos.find(the_id).first::<Photo>(c) { if req.authorized_user().is_some() || tphoto.is_public() {
if req.authorized_user().is_some() || tphoto.is_public() { return render!(res, "templates/details.tpl", {
return render!(res, "templates/details.tpl", { user: Option<String> = req.authorized_user(),
user: Option<String> = req.authorized_user(), lpath: Vec<Link> =
lpath: Vec<Link> = tphoto.date
tphoto.date .map(|d| vec![Link::year(d.year()),
.map(|d| vec![Link::year(d.year()), Link::month(d.year(), d.month() as u8),
Link::month(d.year(), d.month() as u8), Link::day(d.year(), d.month() as u8, d.day())])
Link::day(d.year(), d.month() as u8, d.day())]) .unwrap_or_else(|| vec![]),
.unwrap_or_else(|| vec![]), people: Vec<Person> = {
people: Vec<Person> = { use rphotos::schema::people::dsl::{people, id};
use rphotos::schema::people::dsl::{people, id}; use rphotos::schema::photo_people::dsl::{photo_people, photo_id, person_id};
use rphotos::schema::photo_people::dsl::{photo_people, photo_id, person_id}; people.filter(id.eq_any(photo_people.select(person_id)
people.filter(id.eq_any(photo_people.select(person_id) .filter(photo_id.eq(tphoto.id))))
.filter(photo_id.eq(tphoto.id)))) .load(c).unwrap()
.load(c).unwrap() },
}, places: Vec<Place> = {
places: Vec<Place> = { use rphotos::schema::places::dsl::{places, id};
use rphotos::schema::places::dsl::{places, id}; use rphotos::schema::photo_places::dsl::{photo_places, photo_id, place_id};
use rphotos::schema::photo_places::dsl::{photo_places, photo_id, place_id}; places.filter(id.eq_any(photo_places.select(place_id)
places.filter(id.eq_any(photo_places.select(place_id) .filter(photo_id.eq(tphoto.id))))
.filter(photo_id.eq(tphoto.id)))) .load(c).unwrap()
.load(c).unwrap() },
}, tags: Vec<Tag> = {
tags: Vec<Tag> = { use rphotos::schema::tags::dsl::{tags, id};
use rphotos::schema::tags::dsl::{tags, id}; use rphotos::schema::photo_tags::dsl::{photo_tags, photo_id, tag_id};
use rphotos::schema::photo_tags::dsl::{photo_tags, photo_id, tag_id}; tags.filter(id.eq_any(photo_tags.select(tag_id)
tags.filter(id.eq_any(photo_tags.select(tag_id) .filter(photo_id.eq(tphoto.id))))
.filter(photo_id.eq(tphoto.id)))) .load(c).unwrap()
.load(c).unwrap() },
}, position: Option<Coord> = {
position: Option<Coord> = { use rphotos::schema::positions::dsl::*;
use rphotos::schema::positions::dsl::*; match positions.filter(photo_id.eq(tphoto.id))
match positions.filter(photo_id.eq(tphoto.id)) .select((latitude, longitude))
.select((latitude, longitude)) .first::<(i32, i32)>(c) {
.first::<(i32, i32)>(c) { Ok((tlat, tlong)) => Some(Coord {
Ok((tlat, tlong)) => Some(Coord { x: tlat as f64 / 1e6,
x: tlat as f64 / 1e6, y: tlong as f64 / 1e6,
y: tlong as f64 / 1e6, }),
}), Err(diesel::NotFound) => None,
Err(diesel::NotFound) => None, Err(err) => {
Err(err) => { error!("Failed to read position: {}", err);
error!("Failed to read position: {}", err); None
None
}
} }
}, }
},
time: String = match tphoto.date { time: String = match tphoto.date {
Some(d) => d.format("%T").to_string(), Some(d) => d.format("%T").to_string(),
None => "".to_string() None => "".to_string()
@ -390,9 +423,8 @@ fn photo_details<'mw>(req: &mut Request,
year: Option<i32> = tphoto.date.map(|d| d.year()), year: Option<i32> = tphoto.date.map(|d| d.year()),
month: Option<u32> = tphoto.date.map(|d| d.month()), month: Option<u32> = tphoto.date.map(|d| d.month()),
day: Option<u32> = tphoto.date.map(|d| d.day()), day: Option<u32> = tphoto.date.map(|d| d.day()),
photo: Photo = tphoto photo: Photo = tphoto
}); });
}
} }
} }
res.error(StatusCode::NotFound, "Photo not found") res.error(StatusCode::NotFound, "Photo not found")
@ -450,145 +482,136 @@ fn all_years<'mw>(req: &mut Request,
} }
fn months_in_year<'mw>(req: &mut Request, fn months_in_year<'mw>(req: &mut Request,
res: Response<'mw>) res: Response<'mw>,
year: i32)
-> MiddlewareResult<'mw> { -> MiddlewareResult<'mw> {
use rphotos::schema::photos::dsl::*; use rphotos::schema::photos::dsl::*;
let connection = req.db_conn(); let connection = req.db_conn();
let c: &PgConnection = &connection; let c: &PgConnection = &connection;
if let Ok(year) = req.param("year").unwrap().parse::<i32>() { render!(res, "templates/groups.tpl", {
return render!(res, "templates/groups.tpl", { user: Option<String> = req.authorized_user(),
user: Option<String> = req.authorized_user(), title: String = format!("Photos from {}", year),
title: String = format!("Photos from {}", year), groups: Vec<Group> =
groups: Vec<Group> =
// FIXME only public if not logged on! // FIXME only public if not logged on!
SqlLiteral::new(format!(concat!( SqlLiteral::new(format!(concat!(
"select extract(month from date) m, count(*) c ", "select extract(month from date) m, count(*) c ",
"from photos where extract(year from date)={}{} ", "from photos where extract(year from date)={}{} ",
"group by m order by m"), "group by m order by m"),
year, year,
if req.authorized_user().is_none() { if req.authorized_user().is_none() {
" and grade >= 4" " and grade >= 4"
} else { } else {
"" ""
})) }))
.load::<(Option<f64>, i64)>(c).unwrap() .load::<(Option<f64>, i64)>(c).unwrap()
.iter().map(|&(month, count)| { .iter().map(|&(month, count)| {
let month = month.map(|y| y as u32).unwrap_or(0); let month = month.map(|y| y as u32).unwrap_or(0);
let fromdate = NaiveDate::from_ymd(year, month, 1).and_hms(0, 0, 0); let fromdate = NaiveDate::from_ymd(year, month, 1).and_hms(0, 0, 0);
let todate = let todate =
if month == 12 { NaiveDate::from_ymd(year + 1, 1, 1) } if month == 12 { NaiveDate::from_ymd(year + 1, 1, 1) }
else { NaiveDate::from_ymd(year, month + 1, 1) } else { NaiveDate::from_ymd(year, month + 1, 1) }
.and_hms(0, 0, 0); .and_hms(0, 0, 0);
let photo = photos let photo = photos
// .only_public(req.authorized_user().is_none()) // .only_public(req.authorized_user().is_none())
.filter(date.ge(fromdate)) .filter(date.ge(fromdate))
.filter(date.lt(todate)) .filter(date.lt(todate))
// .filter(path.like("%.JPG")) // .filter(path.like("%.JPG"))
.order((grade.desc(), date.asc())) .order((grade.desc(), date.asc()))
.limit(1) .limit(1)
.first::<Photo>(c).unwrap(); .first::<Photo>(c).unwrap();
Group { Group {
title: monthname(month as u8).to_string(), title: monthname(month as u8).to_string(),
url: format!("/{}/{}/", year, month), url: format!("/{}/{}/", year, month),
count: count, count: count,
photo: photo photo: photo
} }
}).collect() }).collect()
}); })
}
res.error(StatusCode::NotFound, "Not a year")
} }
fn days_in_month<'mw>(req: &mut Request, fn days_in_month<'mw>(req: &mut Request,
res: Response<'mw>) res: Response<'mw>,
year: i32,
month: u8)
-> MiddlewareResult<'mw> { -> MiddlewareResult<'mw> {
use rphotos::schema::photos::dsl::*; use rphotos::schema::photos::dsl::*;
let connection = req.db_conn(); let connection = req.db_conn();
let c: &PgConnection = &connection; let c: &PgConnection = &connection;
if let Ok(year) = req.param("year").unwrap().parse::<i32>() { render!(res, "templates/groups.tpl", {
if let Ok(month) = req.param("month").unwrap().parse::<u8>() { user: Option<String> = req.authorized_user(),
return render!(res, "templates/groups.tpl", { lpath: Vec<Link> = vec![Link::year(year)],
user: Option<String> = req.authorized_user(), title: String = format!("Photos from {} {}", monthname(month), year),
lpath: Vec<Link> = vec![Link::year(year)], groups: Vec<Group> =
title: String = format!("Photos from {} {}", monthname(month), // FIXME only public if not logged on!
year), SqlLiteral::new(format!(concat!(
groups: Vec<Group> = "select extract(day from date) d, count(*) c ",
// FIXME only public if not logged on! "from photos where extract(year from date)={} ",
SqlLiteral::new(format!(concat!( "and extract(month from date)={}{} group by d order by d"),
"select extract(day from date) d, count(*) c ", year, month,
"from photos where extract(year from date)={} ", if req.authorized_user().is_none() {
"and extract(month from date)={}{} group by d order by d"), " and grade >= 4"
year, month, } else {
if req.authorized_user().is_none() { ""
" and grade >= 4" }))
} else { .load::<(Option<f64>, i64)>(c).unwrap()
"" .iter().map(|&(day, count)| {
})) let day = day.map(|y| y as u32).unwrap_or(0);
.load::<(Option<f64>, i64)>(c).unwrap() let fromdate = NaiveDate::from_ymd(year, month as u32, day)
.iter().map(|&(day, count)| { .and_hms(0, 0, 0);
let day = day.map(|y| y as u32).unwrap_or(0); let photo = photos
let fromdate = NaiveDate::from_ymd(year, month as u32, day).and_hms(0, 0, 0); // .only_public(req.authorized_user().is_none())
let photo = photos .filter(date.ge(fromdate))
// .only_public(req.authorized_user().is_none()) .filter(date.lt(fromdate + ChDuration::days(1)))
.filter(date.ge(fromdate)) // .filter(path.like("%.JPG"))
.filter(date.lt(fromdate + ChDuration::days(1))) .order((grade.desc(), date.asc()))
// .filter(path.like("%.JPG")) .limit(1)
.order((grade.desc(), date.asc())) .first::<Photo>(c).unwrap();
.limit(1)
.first::<Photo>(c).unwrap();
Group { Group {
title: format!("{}", day), title: format!("{}", day),
url: format!("/{}/{}/{}", year, month, day), url: format!("/{}/{}/{}", year, month, day),
count: count, count: count,
photo: photo photo: photo
} }
}).collect() }).collect()
}); })
}
}
res.error(StatusCode::NotFound, "Not a month")
} }
fn all_for_day<'mw>(req: &mut Request, fn all_for_day<'mw>(req: &mut Request,
res: Response<'mw>) res: Response<'mw>,
year: i32,
month: u8,
day: u32)
-> MiddlewareResult<'mw> { -> MiddlewareResult<'mw> {
if let Ok(year) = req.param("year").unwrap().parse::<i32>() { let thedate = NaiveDate::from_ymd(year, month as u32, day)
if let Ok(month) = req.param("month").unwrap().parse::<u8>() { .and_hms(0, 0, 0);
if let Ok(day) = req.param("day").unwrap().parse::<u32>() { use rphotos::schema::photos::dsl::*;
let thedate = NaiveDate::from_ymd(year, month as u32, day) let pq = photos
.and_hms(0, 0, 0); .filter(date.ge(thedate))
use rphotos::schema::photos::dsl::*; .filter(date.lt(thedate + ChDuration::days(1)))
let pq = photos // .filter(path.like("%.JPG"))
.filter(date.ge(thedate)) .order((grade.desc(), date.asc()))
.filter(date.lt(thedate + ChDuration::days(1))) .limit(500);
//.filter(path.like("%.JPG"))
.order((grade.desc(), date.asc()))
.limit(500);
let connection = req.db_conn(); let connection = req.db_conn();
let c: &PgConnection = &connection; let c: &PgConnection = &connection;
return render!(res, "templates/index.tpl", { render!(res, "templates/index.tpl", {
user: Option<String> = req.authorized_user(), user: Option<String> = req.authorized_user(),
lpath: Vec<Link> = vec![Link::year(year), lpath: Vec<Link> = vec![Link::year(year),
Link::month(year, month)], Link::month(year, month)],
title: String = format!("Photos from {} {} {}", title: String = format!("Photos from {} {} {}",
day, monthname(month), year), day, monthname(month), year),
photos: Vec<Photo> = photos: Vec<Photo> =
if req.authorized_user().is_none() { if req.authorized_user().is_none() {
pq.filter(grade.ge(&rphotos::models::MIN_PUBLIC_GRADE)) pq.filter(grade.ge(&rphotos::models::MIN_PUBLIC_GRADE))
.load(c).unwrap() .load(c).unwrap()
} else { } else {
pq.load(c).unwrap() pq.load(c).unwrap()
}
});
} }
} })
}
res.error(StatusCode::NotFound, "Not a day")
} }
fn on_this_day<'mw>(req: &mut Request, fn on_this_day<'mw>(req: &mut Request,
@ -602,9 +625,8 @@ fn on_this_day<'mw>(req: &mut Request,
let now = time::now(); let now = time::now();
(now.tm_mon as u8 + 1, now.tm_mday as u32) (now.tm_mon as u8 + 1, now.tm_mday as u32)
}; };
return render!(res, "templates/groups.tpl", { render!(res, "templates/groups.tpl", {
user: Option<String> = req.authorized_user(), user: Option<String> = req.authorized_user(),
//lpath: Vec<Link> = vec![Link::year(year)],
title: String = format!("Photos from {} {}", day, monthname(month)), title: String = format!("Photos from {} {}", day, monthname(month)),
groups: Vec<Group> = groups: Vec<Group> =
SqlLiteral::new(format!(concat!( SqlLiteral::new(format!(concat!(
@ -637,7 +659,7 @@ fn on_this_day<'mw>(req: &mut Request,
photo: photo photo: photo
} }
}).collect() }).collect()
}); })
} }
#[derive(Debug, Clone, RustcEncodable)] #[derive(Debug, Clone, RustcEncodable)]

90
src/nickelext.rs Normal file
View File

@ -0,0 +1,90 @@
//! A module of stuff that might evolve into improvements in nickel
//! itself.
//! Mainly, I'm experimenting with parsing url segments.
macro_rules! wrap2 {
($server:ident.$method:ident /:$slug:ident/, $handler:ident) => {
$server.$method(
concat!("/:", stringify!($slug), "/"),
wrap!($handler: $slug)
)
};
($server:ident.$method:ident /:$slug1:ident/:$slug2:ident/, $handler:ident) => {
$server.$method(
concat!("/:", stringify!($slug1), "/:", stringify!($slug2), "/"),
wrap!($handler: $slug1, $slug2)
)
};
($server:ident.$method:ident /:$slug1:ident/:$slug2:ident/:$slug3:ident/, $handler:ident) => {
$server.$method(
concat!("/:", stringify!($slug1), "/:", stringify!($slug2),
"/:", stringify!($slug3)),
wrap!($handler: $slug1, $slug2, $slug3)
)
};
($server:ident.$method:ident /$path:ident, $handler:ident ) => {
$server.$method(concat!("/", stringify!($path)), $handler)
};
($server:ident.$method:ident /$path:ident/, $handler:ident ) => {
$server.$method(concat!("/", stringify!($path), "/"), $handler)
};
($server:ident.$method:ident /$path:ident/:$slug:ident, $handler:ident) => {
$server.$method(
concat!("/", stringify!($path), "/:", stringify!($slug)),
wrap!($handler: $slug)
)
};
($server:ident.$method:ident /$path:ident/:$slug:ident/:$slug2:ident, $handler:ident) => {
$server.$method(
concat!("/", stringify!($path), "/:", stringify!($slug),
"/:", stringify!($slug2)),
wrap!($handler: $slug, $slug2)
)
};
}
macro_rules! wrap {
($handler:ident : $( $param:ident ),+ ) => { {
#[allow(unused_parens)]
fn wrapped<'mw>(req: &mut Request,
res: Response<'mw>)
-> MiddlewareResult<'mw> {
if let ($(Some($param),)*) = ($(opt(req, stringify!($param)),)*) {
$handler(req, res, $($param),*)
} else {
res.error(StatusCode::NotFound, "Parameter mismatch")
}
}
wrapped
} }
}
pub trait FromSlug : Sized {
fn parse_slug(slug: &str) -> Option<Self>;
}
impl FromSlug for String {
fn parse_slug(slug: &str) -> Option<Self> {
Some(slug.to_string())
}
}
impl FromSlug for i32 {
fn parse_slug(slug: &str) -> Option<Self> {
slug.parse::<Self>().ok()
}
}
impl FromSlug for u8 {
fn parse_slug(slug: &str) -> Option<Self> {
slug.parse::<Self>().ok()
}
}
impl FromSlug for u16 {
fn parse_slug(slug: &str) -> Option<Self> {
slug.parse::<Self>().ok()
}
}
impl FromSlug for u32 {
fn parse_slug(slug: &str) -> Option<Self> {
slug.parse::<Self>().ok()
}
}