Macro anterofit::service[][src]

macro_rules! service {
    (
        $(#[$meta:meta])*
        trait $servicenm:ident {
            $($guts:tt)*
        }
    ) => { ... };
    (
        $(#[$meta:meta])*
        pub trait $servicenm:ident {
            $($guts:tt)*
        }
    ) => { ... };
    (
        $(#[$meta:meta])*
        trait $servicenm:ident {
            $($guts:tt)*
        }

        $($delegates:tt)+
    ) => { ... };
    (
        $(#[$meta:meta])*
        pub trait $servicenm:ident {
            $($guts:tt)*
        }

        $($delegates:tt)+
    ) => { ... };
}

Define a service trait whose methods make HTTP requests.

##Example

pub type ApiToken = String;

service! {
    pub trait MyService {
        /// Get the version of this API.
        fn api_version(&self) -> String {
            GET("/version")
        }

        /// Register a new user with the API.
        fn register(&self, username: &str, password: &str) {
            POST("/register");
            fields! {
                username, password
            }
        }

        /// Login an existing user with the API, returning the API token.
        fn login(&self, username: &str, password: &str) -> ApiToken {
            POST("/login");
            fields! {
                username, password
            }
        }
    }
}

##Generics and where clauses Both of these are supported; however, the Rust grammar must be changed slightly so that they can be parsed and transformed properly by the service!{} macro without making its implementation details too complex.

Put simply, use [] instead of <> to wrap your generic declarations, and wrap your entire where clause, if present, with []:

pub type ApiToken = String;

service! {
    pub trait MyService {
        /// Register a new user with the API.
        fn register[U: ToString, P: ToString](&self, username: U, password: P) {
            POST("/register");
            fields! {
                username, password
            }
        }

        /// Login an existing user with the API.
        fn login[U, P](&self, username: U, password: P) -> ApiToken
        [where U: ToString, P: ToString] {
            POST("/login");
            fields! {
                username, password
            }
        }
    }
}

##Delegates By default, every service trait declared with service!{} has a blanket-impl for T: anterofit::AbsAdapter, which makes it most useful for the default use-case, where you're using Anterofit to make HTTP requests within your application.

However, if you want to use Anterofit to create a library wrapping some REST API, such as Github's, this blanket impl is not so useful as you will probably want to create your own wrapper for Anterofit's adapter that always uses the correct base URL, serializer/deserializer, adds auth tokens, etc.

In this case, you can declare one or more delegate impls which will be used instead of the default blanket impl; the only requirement of these delegate impl declarations is that they provide an accessor for an underlying AbsAdapter implementation (which is only required to be visible to the declaring module, allowing an opaque abstraction while using service traits in a public API). The accessor is an expression that resolves to an FnOnce() closure which is passed the self parameter, and is expected to return &T where T: AbsAdapter.

extern crate rustc_serialize;

use anterofit::{Adapter, JsonAdapter, Url};

pub struct DelegateAdapter {
    // Notice that this field remains private but due to visibility rules,
    // the impls of `DelegatedService` still get to access it.
    // This allows you to hide the adapter as an implementation detail.
    inner: JsonAdapter
}

impl DelegateAdapter {
    pub fn new() -> Self {
        let adapter = Adapter::builder()
            .serialize_json()
            .base_url(Url::parse("https://myservice.com").unwrap())
            .build();

        DelegateAdapter {
            inner: adapter,
        }
    }
}

// If using the `serde` feature, you would use `#[derive(Deserialize)]` instead
// and `extern crate serde;` at the crate root.
#[derive(RustcDecodable)]
pub struct Record {
    pub id: u64,
    pub title: String,
    pub body: String,
}

service! {
    pub trait DelegatedService {
        /// Create a new record, returning the record ID.
        fn create_record(&self, title: &str, body: &str) -> u64 {
            POST("/record");
            fields! { title, body }
        }

        /// Get an existing record by ID.
        fn get_record(&self, record_id: u64) -> Record {
            GET("/record/{}", record_id)
        }
    }

    // This generates `impl DelegatedService for DelegateAdapter {}`
    impl for DelegateAdapter {
        // Closure parameter is just `&self` from the service method body.
        |this| &this.inner
    }

    // Generics and `where` clauses are allowed in their usual positions, however `[]` is
    // required in the same places as mentioned under the previous header.
    impl[T] for T [where T: AsRef<DelegateAdapter>] {
        |this| &this.as_ref().inner
    }

    // As shown here, multiple declarations are allowed as well.
}