use collections::tree_map::TreeMap;
use serialize::json;
use serialize::json::{Object};
use typemap::TypeMap;
use queryst;
use backend::{Request, Response};
use server::status::StatusCode;
use server::header::common::Accept;
use errors::{Error, ErrorRefExt, NotMatchError, NotAcceptableError, QueryStringDecodeError, BodyDecodeError};
use backend::{Handler, HandleResult, HandleSuccessResult};
use framework::nesting::Nesting;
use framework::media::Media;
use framework::{ApiHandler, ApiHandlers, Callbacks, CallInfo, ErrorFormatters, ErrorFormatter};
use framework::formatters;
#[allow(dead_code)]
pub enum Versioning {
Path,
AcceptHeader(&'static str),
Param(&'static str)
}
#[deriving(Send)]
pub struct Api {
pub version: Option<String>,
pub versioning: Option<Versioning>,
pub prefix: String,
handlers: ApiHandlers,
before: Callbacks,
before_validation: Callbacks,
after_validation: Callbacks,
after: Callbacks,
error_formatters: ErrorFormatters,
default_error_formatters: ErrorFormatters
}
impl Api {
pub fn new() -> Api {
Api {
version: None,
versioning: None,
prefix: "".to_string(),
handlers: vec![],
before: vec![],
before_validation: vec![],
after_validation: vec![],
after: vec![],
error_formatters: vec![],
default_error_formatters: vec![formatters::validation_error_formatter]
}
}
pub fn build(builder: |&mut Api|) -> Api {
let mut api = Api::new();
builder(&mut api);
return api;
}
pub fn version(&mut self, version: &str, versioning: Versioning) {
self.version = Some(version.to_string());
self.versioning = Some(versioning);
}
pub fn prefix(&mut self, prefix: &str) {
self.prefix = prefix.to_string();
}
pub fn error_formatter(&mut self, formatter: ErrorFormatter) {
self.error_formatters.push(formatter);
}
fn handle_error(&self, err: &Box<Error>, media: &Media) -> Option<Response> {
for err_formatter in self.error_formatters.iter() {
match (*err_formatter)(err, media) {
Some(resp) => return Some(resp),
None => ()
}
}
for err_formatter in self.default_error_formatters.iter() {
match (*err_formatter)(err, media) {
Some(resp) => return Some(resp),
None => ()
}
}
None
}
fn extract_media(&self, req: &Request) -> Option<Media> {
let header = req.headers().get::<Accept>();
match header {
Some(&Accept(ref mimes)) if !mimes.is_empty() => {
Some(Media::from_mime(&mimes[0]))
},
_ => Some(Media::default())
}
}
fn parse_query(query_str: &str, params: &mut Object) -> HandleSuccessResult {
let maybe_query_params = queryst::parse(query_str);
match maybe_query_params {
Ok(query_params) => {
for (key, value) in query_params.as_object().unwrap().iter() {
if !params.contains_key(key) {
params.insert(key.to_string(), value.clone());
}
}
},
Err(_) => {
return Err(box QueryStringDecodeError as Box<Error>);
}
}
Ok(())
}
fn parse_json_body(req: &mut Request, params: &mut Object) -> HandleSuccessResult {
let utf8_string_body = match String::from_utf8(req.body().clone()) {
Ok(e) => e,
Err(_) => return Err(box BodyDecodeError::new("Invalid UTF-8 sequence".to_string()) as Box<Error>),
};
if utf8_string_body.len() > 0 {
let maybe_json_body = json::from_str(utf8_string_body.as_slice());
match maybe_json_body {
Ok(json_body) => {
for (key, value) in json_body.as_object().unwrap().iter() {
if !params.contains_key(key) {
params.insert(key.to_string(), value.clone());
}
}
},
Err(err) => return Err(box BodyDecodeError::new(format!("{}", err)) as Box<Error>)
}
}
Ok(())
}
fn parse_request(req: &mut Request, params: &mut Object) -> HandleSuccessResult {
if req.url().query().is_some() {
try!(Api::parse_query(req.url().query().as_ref().unwrap().as_slice(), params));
}
if req.is_json_body() {
try!(Api::parse_json_body(req, params));
}
Ok(())
}
#[allow(unused_variables)]
pub fn call(&self, rest_path: &str, req: &mut Request, app: &Application) -> HandleResult<Response> {
let mut params = TreeMap::new();
try!(Api::parse_request(req, &mut params));
match self.api_call(rest_path, &mut params, req, &mut CallInfo::new(app)) {
Ok(resp) => Ok(resp),
Err(err) => {
if err.downcast::<NotMatchError>().is_none() {
match self.handle_error(&err, &self.extract_media(req).unwrap_or_else(|| Media::default())) {
Some(resp) => Ok(resp),
None => Err(err)
}
} else {
Err(err)
}
}
}
}
}
impl Nesting for Api {
fn get_handlers<'a>(&'a self) -> &'a ApiHandlers { &self.handlers }
fn get_handlers_mut<'a>(&'a mut self) -> &'a mut ApiHandlers { &mut self.handlers }
fn get_before<'a>(&'a self) -> &'a Callbacks { &self.before }
fn get_before_mut<'a>(&'a mut self) -> &'a mut Callbacks { &mut self.before }
fn get_before_validation<'a>(&'a self) -> &'a Callbacks { &self.before_validation }
fn get_before_validation_mut<'a>(&'a mut self) -> &'a mut Callbacks { &mut self.before_validation }
fn get_after_validation<'a>(&'a self) -> &'a Callbacks { &self.after_validation }
fn get_after_validation_mut<'a>(&'a mut self) -> &'a mut Callbacks { &mut self.after_validation }
fn get_after<'a>(&'a self) -> &'a Callbacks { &self.after }
fn get_after_mut<'a>(&'a mut self) -> &'a mut Callbacks { &mut self.after }
}
impl ApiHandler for Api {
fn api_call(&self, rest_path: &str, params: &mut Object, req: &mut Request, info: &mut CallInfo) -> HandleResult<Response> {
let mut rest_path = if self.prefix.len() > 0 {
if rest_path.slice_from(1).starts_with(self.prefix.as_slice()) {
rest_path.slice_from(self.prefix.len() + 1)
} else {
return Err(box NotMatchError as Box<Error>)
}
} else {
rest_path
};
let mut media: Option<Media> = None;
if self.version.is_some() {
let version = self.version.as_ref().unwrap();
let versioning = self.versioning.as_ref().unwrap();
match versioning {
&Versioning::Path => {
if rest_path.slice_from(1).starts_with(version.as_slice()) {
rest_path = rest_path.slice_from(version.len() + 1)
} else {
return Err(box NotMatchError as Box<Error>)
}
},
&Versioning::Param(ref param_name) => {
match params.get(*param_name) {
Some(obj) if obj.is_string() && obj.as_string().unwrap() == version.as_slice() => (),
_ => return Err(box NotMatchError as Box<Error>)
}
},
&Versioning::AcceptHeader(ref vendor) => {
let header = req.headers().get::<Accept>();
match header {
Some(&Accept(ref mimes)) => {
let mut matched_media: Option<Media> = None;
for mime in mimes.iter() {
match Media::from_vendor(mime) {
Some(media) => {
if media.vendor.as_slice() == *vendor &&
media.version.is_some() &&
media.version.as_ref().unwrap() == version {
matched_media = Some(media);
break;
}
},
None => ()
}
}
if matched_media.is_none() {
return Err(box NotMatchError as Box<Error>)
} else {
media = matched_media;
}
},
None => return Err(box NotMatchError as Box<Error>)
}
}
}
}
if media.is_none() {
match self.extract_media(req) {
Some(media) => {
info.media = media
},
None => return Err(box NotAcceptableError as Box<Error>)
}
}
self.push_callbacks(info);
self.call_handlers(rest_path, params, req, info)
}
}
#[deriving(Send)]
pub struct Application {
pub ext: TypeMap,
pub root_api: Api
}
impl Application {
pub fn new(root_api: Api) -> Application {
Application {
root_api: root_api,
ext: TypeMap::new()
}
}
pub fn call(&self, req: &mut Request) -> HandleResult<Response> {
self.root_api.call(("/".to_string() + req.url().path().connect("/")).as_slice(), req, self)
}
pub fn call_with_not_found(&self, req: &mut Request) -> HandleResult<Response> {
let res = self.call(req);
match res {
Ok(res) => Ok(res),
Err(err) => {
if err.downcast::<NotMatchError>().is_some() {
return Ok(Response::from_string(StatusCode::NotFound, "".to_string()));
}
return Err(err);
}
}
}
}