surwiki/src/wiki_lookup.rs

154 lines
4.8 KiB
Rust
Raw Normal View History

2017-10-15 18:31:11 +03:00
use std::borrow::Cow;
2017-09-15 18:28:23 +03:00
use std::collections::HashMap;
2017-10-15 18:31:11 +03:00
use std::str::Utf8Error;
2017-09-15 18:28:23 +03:00
use futures::{Future, finished, failed, done};
2017-10-15 18:31:11 +03:00
use futures::future::FutureResult;
2017-10-02 00:24:16 +03:00
use percent_encoding::percent_decode;
2017-10-15 18:31:11 +03:00
use slug::slugify;
2017-09-15 18:28:23 +03:00
2017-10-13 16:21:23 +03:00
use resources::*;
use assets::*;
2017-09-15 18:28:23 +03:00
use state::State;
use web::{Lookup, Resource};
2017-10-01 23:35:06 +03:00
type BoxResource = Box<Resource + Sync + Send>;
2017-10-15 15:26:22 +03:00
type ResourceFn = Box<Fn() -> BoxResource + Sync + Send>;
2017-10-01 23:35:06 +03:00
2017-09-15 18:28:23 +03:00
lazy_static! {
2017-10-15 17:49:55 +03:00
static ref ASSETS_MAP: HashMap<String, ResourceFn> = {
let mut map = HashMap::new();
2017-09-15 18:28:23 +03:00
2017-10-15 17:49:55 +03:00
map.insert(
2017-10-15 15:26:22 +03:00
format!("style-{}.css", StyleCss::checksum()),
Box::new(|| Box::new(StyleCss) as BoxResource) as ResourceFn
2017-10-03 11:37:18 +03:00
);
2017-10-15 17:49:55 +03:00
map.insert(
2017-10-15 15:26:22 +03:00
format!("script-{}.js", ScriptJs::checksum()),
Box::new(|| Box::new(ScriptJs) as BoxResource) as ResourceFn
2017-10-13 16:06:16 +03:00
);
2017-10-15 17:49:55 +03:00
map.insert(
2017-10-15 15:26:22 +03:00
format!("amatic-sc-v9-latin-regular.woff"),
Box::new(|| Box::new(AmaticFont) as BoxResource) as ResourceFn
2017-09-15 18:28:23 +03:00
);
2017-10-15 17:49:55 +03:00
map
2017-09-15 18:28:23 +03:00
};
}
#[derive(Clone)]
pub struct WikiLookup {
state: State
}
2017-10-15 18:31:11 +03:00
fn split_one(path: &str) -> Result<(Cow<str>, Option<&str>), Utf8Error> {
2017-10-14 16:13:03 +03:00
let mut split = path.splitn(2, '/');
let head = split.next().expect("At least one item must be returned");
let head = percent_decode(head.as_bytes()).decode_utf8()?;
let tail = split.next();
Ok((head, tail))
}
2017-10-15 18:31:11 +03:00
fn asset_lookup(path: &str) -> FutureResult<Option<BoxResource>, Box<::std::error::Error + Send + Sync>> {
2017-10-15 17:49:55 +03:00
let (head, tail) = match split_one(path) {
Ok(x) => x,
Err(x) => return failed(x.into()),
};
if tail.is_some() {
return finished(None);
}
match ASSETS_MAP.get(head.as_ref()) {
2017-10-15 15:26:22 +03:00
Some(resource_fn) => finished(Some(resource_fn())),
None => finished(None),
}
}
2017-09-15 18:28:23 +03:00
impl WikiLookup {
pub fn new(state: State) -> WikiLookup {
WikiLookup { state }
}
fn reserved_lookup(&self, path: &str, query: Option<&str>) -> <Self as Lookup>::Future {
2017-10-15 17:34:27 +03:00
let (head, tail) = match split_one(path) {
Ok(x) => x,
Err(x) => return Box::new(failed(x.into())),
};
match (head.as_ref(), tail) {
("_assets", Some(asset)) =>
2017-10-15 15:26:22 +03:00
Box::new(asset_lookup(asset)),
("_changes", None) => {
let state = self.state.clone();
Box::new(
done(pagination::from_str(query.unwrap_or("")).map_err(Into::into))
.and_then(move |pagination| ChangesResource::new(state, pagination))
.and_then(|changes_resource| Ok(Some(Box::new(changes_resource) as BoxResource)))
)
},
2017-10-15 17:34:27 +03:00
("_new", None) =>
2017-10-15 15:26:22 +03:00
Box::new(finished(Some(Box::new(NewArticleResource::new(self.state.clone(), None)) as BoxResource))),
2017-10-15 17:34:27 +03:00
("_sitemap", None) =>
2017-10-15 15:26:22 +03:00
Box::new(finished(Some(Box::new(SitemapResource::new(self.state.clone())) as BoxResource))),
2017-10-15 17:34:27 +03:00
_ => Box::new(finished(None)),
2017-10-15 15:26:22 +03:00
}
2017-10-14 16:13:03 +03:00
}
2017-09-17 12:27:50 +03:00
2017-10-14 16:13:03 +03:00
fn article_lookup(&self, path: &str, query: Option<&str>) -> <Self as Lookup>::Future {
let (slug, tail) = match split_one(path) {
2017-10-02 00:24:16 +03:00
Ok(x) => x,
2017-10-14 16:13:03 +03:00
Err(x) => return Box::new(failed(x.into())),
};
2017-09-17 12:27:50 +03:00
2017-10-14 16:13:03 +03:00
if tail.is_some() {
2017-09-17 12:27:50 +03:00
// Currently disallow any URLs of the form /slug/...
2017-09-17 16:43:31 +03:00
return Box::new(finished(None));
2017-09-17 12:27:50 +03:00
}
// Normalize all user-generated slugs:
2017-10-15 18:31:11 +03:00
let slugified_slug = slugify(&slug);
if slugified_slug != slug {
return Box::new(finished(Some(
2017-10-01 23:35:06 +03:00
Box::new(ArticleRedirectResource::new(slugified_slug)) as BoxResource
)));
}
let state = self.state.clone();
let edit = query == Some("edit");
2017-10-14 16:13:03 +03:00
let slug = slug.into_owned();
use state::SlugLookup;
Box::new(self.state.lookup_slug(slug.clone())
.and_then(move |x| Ok(Some(match x {
SlugLookup::Miss =>
Box::new(NewArticleResource::new(state, Some(slug))) as BoxResource,
SlugLookup::Hit { article_id, revision } =>
Box::new(ArticleResource::new(state, article_id, revision, edit)) as BoxResource,
SlugLookup::Redirect(slug) =>
2017-10-01 23:35:06 +03:00
Box::new(ArticleRedirectResource::new(slug)) as BoxResource,
})))
)
2017-09-15 18:28:23 +03:00
}
}
2017-10-14 16:13:03 +03:00
impl Lookup for WikiLookup {
type Resource = BoxResource;
type Error = Box<::std::error::Error + Send + Sync>;
type Future = Box<Future<Item = Option<Self::Resource>, Error = Self::Error>>;
fn lookup(&self, path: &str, query: Option<&str>) -> Self::Future {
assert!(path.starts_with("/"));
let path = &path[1..];
if path.starts_with("_") {
self.reserved_lookup(path, query)
} else {
self.article_lookup(path, query)
}
}
}