reep 0.2.0

[deprecated] REsource EndPoint (REEP): Generic Iron Endpoint for RESTlike Resource Access
Documentation
use std::marker::PhantomData;
use std::any::Any;
use bit_set::BitSet;

use iron::Handler;
use iron::middleware::Chain;
use iron::{Request, Response, IronResult};
use iron::method::Method;
use router::Router;


use self::ResourceMethod::*;
use internals::{Parsers, OptionHandler};
use types::*;


const BASE_PATH: &'static str = "/";
const ID_PATH: &'static str = "/:resource_id";

/// The ResourceMethod is like a Http Method but in the
/// Context of a Resource. Expect the nomral Methods there
/// are Hooks for defining Head and Option Hanlders
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum ResourceMethod {
    /// Referes to Creating a Resource (Http Post on /)
    Create,
    /// Referes to Listing all Resources (Http Get on /)
    List,
    /// Referes to Accessing a single Resource by id (Http Get on /:resource_id)
    Get,
    /// Referes to Replacing a single Resource by id (Http Put on /:resource_id)
    Replace,
    /// Referes to Deleting a single Resource by id (Http Delete on /:resource_id)
    Delete,
    /// Use to define a Http Head Method Handler on /
    HttpHead,
    /// Use to define a Http Head Method Handler on /:resource_id
    HttpHeadList,
    /// Use to define a Http Option Method Handler on / (simple one is supplied by default)
    CustomHttpOptions,
    /// Use to define a Http Option Method Handler on /:resource_id (simple one is supplied by default)
    CustomHttpOptionsList
}

impl ResourceMethod {
    fn bitset_id(&self) -> u16 {
        *self as u16
    }

    fn info(&self) -> (Method, &'static str, &'static str) {
        match *self {
            Create => (Method::Post, BASE_PATH, "create"),
            List => (Method::Get, BASE_PATH, "list"),
            ResourceMethod::Get => (Method::Get, ID_PATH, "get"),
            Replace => (Method::Put, ID_PATH, "put"),
            Delete => (Method::Delete, ID_PATH, "delete"),
            HttpHead => (Method::Head, ID_PATH, "head"),
            HttpHeadList => (Method::Head, BASE_PATH, "list"),
            CustomHttpOptions => (Method::Options, ID_PATH, "options"),
            CustomHttpOptionsList => (Method::Options, BASE_PATH, "list_options")
        }
    }
}


/// The ResourceEndoint struct on itself is just a Wrapper around Router.
/// It can be Treated like any other Handler and e.g. routed. The Resource
/// endpint itself uses a / and a /:resource_id therefor it is normal to
/// use a routing/mounting middelware to let it run on e.g. /users/ /users/:resource_id
pub struct ResourceEndpoint(Router);


impl Handler for ResourceEndpoint {
    fn handle(&self, req: &mut Request) -> IronResult<Response> {
        self.0.handle(req)
    }
}


/// The ResourceEndpointBuilder is the only provieded way to create a ResourceEndpoint
/// it will automatikly inject the gicen body parser and options parser in the routs witch
/// need them. Same is true for a IdParser internaly created using Id type provided by the
/// resource
pub struct ResourceEndpointBuilder<I: Id+Any, OpP: ParserMiddleware, R: Resource, BoP: ParserMiddleware> {
    _id_info: PhantomData<I>,
    _resource: PhantomData<R>,
    route_id_base: String,
    parsers: Parsers<OpP, BoP>,
    seted_values: BitSet,
    router: Router
}

impl
    <OpP: OptionParser, R: Resource, BoP: BodyParser<R>>
    ResourceEndpointBuilder<R::RId, OpP, R, BoP>
    where R::RId: Sync+Send+Any
{

    /// create a new ResourceEndpointBuilder
    /// The restrictions on the generic types and their Associatvie Types will assure that
    /// Resource, Resource Id, IdParser and BodyParser fit together
    /// IdIndo is soly used to provide the IdType and a Key Type for accessing it over the
    /// reqest.extension typemap.
    pub fn new(route_id_base: String, option_parser: OpP, body_parser: BoP)
        -> ResourceEndpointBuilder<R::RId, OpP, R, BoP>
    {
        ResourceEndpointBuilder {
            _resource: PhantomData,
            _id_info: PhantomData,
            route_id_base: route_id_base,
            parsers: Parsers::new::<R::RId>(option_parser, body_parser),
            router: Router::new(),
            seted_values: BitSet::new()
        }
    }

    /// Check if a  handler/Method for givven ResourceMethod is set
    pub fn is_method_set(&self, method: ResourceMethod) -> bool {
        self.seted_values.contains(method.bitset_id() as usize)
    }

    /// Consumes the Builder to Create the final produkt
    /// simple implementation for CustomHttpOptions/CustomHttpOptionsList will be added
    /// if not provided when building
    pub fn finalize(mut self) -> ResourceEndpoint {

        if !self.is_method_set(CustomHttpOptions) {
            let mut allowed_methods = vec![Method::Options];
            if self.is_method_set(Delete) {
                allowed_methods.push(Delete.info().0);
            }
            if self.is_method_set(Replace) {
                allowed_methods.push(Replace.info().0);
            }
            if self.is_method_set(Get) {
                allowed_methods.push(Get.info().0);
            }
            self = self.route(CustomHttpOptions, OptionHandler::new(allowed_methods));
        }
        if !self.is_method_set(CustomHttpOptionsList) {
            let mut allowed_methods = vec![Method::Options];
            if self.is_method_set(List) {
                allowed_methods.push(List.info().0);
            }
            if self.is_method_set(Create) {
                allowed_methods.push(Create.info().0);
            }
            self = self.route(CustomHttpOptionsList, OptionHandler::new(allowed_methods));
        }

        ResourceEndpoint(self.router)
    }

    /// attach a handler for a given ResourceMethod
    /// Depending on the handler the Body, Id and Option Parsers might be injected
    /// as BeforeMiddleware
    ///
    /// ## Panic
    /// Panics if a handler was already provided for the Methodd.
    /// Use is_method_set to check if you cann't be sure
    pub fn route<H: Handler>(mut self, resource_method: ResourceMethod, handler: H) -> Self {
        if !self.seted_values.insert(resource_method.bitset_id() as usize) {
            panic!("ResourceMethod [{:?}] was already set", resource_method);
        }
        let (method, route, rout_id_postfix) = resource_method.info();
        let chain = self.create_chain(resource_method, handler);

        let mut rout_id = self.route_id_base.clone();
        rout_id.push_str(rout_id_postfix);
        self.router.route(method, route, chain, rout_id);
        self
    }

    fn create_chain<H: Handler>(&self, meth: ResourceMethod, handler: H) -> Chain {
        match meth {
            Create => chain_with_body(&self.parsers, handler),
            List => chain(&self.parsers, handler),
            Get => chain_with_id(&self.parsers, handler),
            Replace => chain_with_id_body(&self.parsers, handler),
            Delete => chain_with_id(&self.parsers, handler),
            HttpHead => chain_with_id(&self.parsers, handler),
            HttpHeadList => chain(&self.parsers, handler),
            CustomHttpOptions => chain_with_id(&self.parsers, handler),
            CustomHttpOptionsList => chain(&self.parsers, handler)
        }
    }

    ///shorthand for route(ResourceMethod::Create, handler)
    #[inline]
    pub fn create<H: Handler>(self, handler: H) -> Self {
        self.route(Create, handler)
    }

    ///shorthand for route(ResourceMethod::List, handler)
    #[inline]
    pub fn list<H: Handler>(self, handler: H) -> Self {
        self.route(List, handler)
    }

    ///shorthand for route(ResourceMethod::Delete, handler)
    #[inline]
    pub fn delete<H: Handler>(self, handler: H) -> Self {
        self.route(Delete, handler)
    }

    ///shorthand for route(ResourceMethod::Replace, handler)
    #[inline]
    pub fn replace<H: Handler>(self, handler: H) -> Self {
        self.route(Replace, handler)
    }

    ///shorthand for route(ResourceMethod::Get, handler)
    #[inline]
    pub fn get<H: Handler>(self, handler: H) -> Self {
        self.route(Get, handler)
    }
}

fn chain<OpP, BoP, H>(parsers: &Parsers<OpP, BoP>, handler: H) -> Chain
    where OpP: ParserMiddleware, BoP: ParserMiddleware, H: Handler
{
    let mut chain = Chain::new(handler);
    chain.link_before(parsers.option_parser.clone());
    chain
}
fn chain_with_id<OpP, BoP, H>(parsers: &Parsers<OpP, BoP>, handler: H) -> Chain
    where OpP: ParserMiddleware, BoP: ParserMiddleware, H: Handler
{
    let mut chain = Chain::new(handler);
    chain.link_before(parsers.option_parser.clone());
    chain.link_before(parsers.id_parser.clone());
    chain
}
fn chain_with_body<OpP, BoP, H>(parsers: &Parsers<OpP, BoP>, handler: H) -> Chain
    where OpP: ParserMiddleware, BoP: ParserMiddleware, H: Handler
{
    let mut chain = Chain::new(handler);
    chain.link_before(parsers.option_parser.clone());
    chain.link_before(parsers.body_parser.clone());
    chain
}
fn chain_with_id_body<OpP, BoP, H>(parsers: &Parsers<OpP, BoP>, handler: H) -> Chain
    where OpP: ParserMiddleware, BoP: ParserMiddleware, H: Handler
{
    let mut chain = Chain::new(handler);
    chain.link_before(parsers.option_parser.clone());
    chain.link_before(parsers.id_parser.clone());
    chain.link_before(parsers.body_parser.clone());
    //TODO link in middleware checking &id == body.id_ref()
    chain
}

//cr option + body
//qu option x2
//ge option + id x2
//de option + id
//re option + id + body