foundation 0.0.1

A Rust flavored replacement for the classic cowsay
Documentation
// Import the crate. You need the #[macro_use] or none of this will work.
// Needs to be in the crate root
#[macro_use] extern crate foundation;
// Bring crates used into scope, use only in the crate root. Not fun part this
// will tie you to specific versions of the crates. Since we reexport stuff from
// them as part of the public API. This is cleaner than importing the huge
// amount of types yourself.
use foundation::crates::*;
// Use this anywhere there are macros being used from foundation
use foundation::prelude::*;

// We'll need this in the code below. It's not required for foundation
extern crate serde_json;
use serde_json::Value;

// Name of your client type
// A string that points to the API
// A closure that takes the below parameters (one being the name of your client
// type), that allows you to manipulate the `Request` type from Hyper. Perfect
// for setting up required headers or handling a given token value for all
// requests that are made to the API. It shouldn't return a value.
client!(
    GitHub,
    "https://api.github.com/",
    |request: &mut Request, client: &GitHub| {
        let token = String::from("token ") + &client.token;
        let mime = "application/vnd.github.v3+json".parse().unwrap();
        let headers = request.headers_mut();
        headers.set(ContentType::json());
        headers.set(UserAgent::new(String::from("github-rs")));
        headers.set(Accept(vec![qitem(mime)]));
        headers.set(Authorization(token));
    }
);

// Function used for deserialiation using this signature header. Can have
// multiple of these functions if needed (i.e. one for XML, another for JSON,
// or one for MsgPack)
fn string_from_bytes<T>(bytes: &[u8]) -> Result<T, FoundationError>
    where T: DeserializeOwned // From Serde. As long as your deserialization
                              // implements serde's traits you're good to go!
{
    serde_json::from_slice(bytes)
        .map_err(|e| FoundationError::Deserialization(e.to_string()))
}

// Define new types, where a type represents a part of the URL. If this part
// of the URL built up can execute a request give it a path to your
// deserialization function in order for it to work when executed.
// You can specify a type that doesn't represent a valid query, but part of
// a URL with something like this:
// new_type!(
//     NotDeserializableType;
//     User => string_from_bytes;
//     Users => string_from_bytes;
//     UsersUsername => string_from_bytes;
// )
// as part of the list.
new_type!(
    User => string_from_bytes;
    Users => string_from_bytes;
    UsersUsername => string_from_bytes;
);

// Here's where you wire everything up between the types. We know what can
// deserialize and what's just a type, but not how to go from one part of the
// URL to the other. You have a few different ways to do this. First thing is
// you name what type you want to make go to another by naming it like so:
//
// MyType {
//
// };
//
// Inside of it you can put two different types of fields:
// 1) parameter_transition
// 2) transition
//
// parameter_transition when utilized looks like this:
//
// MyType {
//     parameter_transition {
//         (MyType2, my_type_two, type_str)
//         (MyType3, my_type_three, type_str)
//     }
// };
//
// It takes a list of tuples. The first value is the type you want to go to,
// the second value is the name of the function used to make the transition,
// and the third value is the name of the parameter in the function, that shows
// up in the documentation you can generate.
//
// The transition field when utilized looks like this:
//
// MyType {
//     transition {
//         (MyType2, ty_two)
//         (MyType3, ty_three)
//     }
// };
//
// It takes a list of tuples. The first value is the type you want to go to,
// the second value is the name of the function used to make the transition and
// the string used for the URL. So if you were at https://mysite.com/ and you
// called ty_two while building up the query it becomes
// https://mysite.com/ty_two used for the URL.
//
// You can also use both together! The transition field comes before the
// parameter_transition field
// MyType {
//     transition {
//         (MyType2, ty_two)
//         (MyType3, ty_three)
//     }
//
//     parameter_transition {
//         (MyType4, my_type_four, type_str)
//         (MyType5, my_type_five, type_str)
//     }
// };
//
// You can now repeat the above pattern for every type you need. By default
// in the file you call `client!()` you get a Get, Post, Put, Patch, and Delete
// type that can be wired up to other types. You'll need to wire these up to
// whatever other types you need to or else you'll never be able to make a
// query!
make_connections!(
    Users {
        parameter_transition {
            (UsersUsername, username, username_str)
        }
    };

    Get {
        transition {
            (User, user)
            (Users, users)
        }
    };
);

fn main() {

    // First we crate our client and hand a token to it. Generally this is
    // how most Web APIs work. We put code above to handle headers and this
    // token. Of course you'll need to put in a custom access token here
    // to work as we won't commit our own. OPSEC 101. Put one here as a value
    // that implements `ToString` to try this example out.
    let client = GitHub::new("Use your own token")
                        .unwrap();

    // The neat thing is that the `execute` function is part of a trait called
    // `Executor` so if you want to handle queries in a standard way or in
    // different ways for different types you can do that with something like
    // below! We'll use it on our three queries to actually print all the
    // information returned!
    pub trait TryExecute: Executor where Self: Sized {
        fn try_execute(self) {
            // Execute our query and deserialize it to the Value type from
            // serde_json
            match self.execute::<Value>() {
                Ok((headers, status, json)) => {
                    println!("{}", headers);
                    println!("{}", status);

                    // Sometimes queries don't return a body in the response
                    // so we had to make the type an Option<T> in order to
                    // handle that possibility.
                    if let Some(json) = json {
                        println!("{}", json)
                    }
                },
                Err(e) => println!("{}", e),
            }
        }
    }

    // Soooo a lot of this lifetime stuff is hidden because ewww but this is
    // something that might be unavoidable for you. 'f is used around the
    // codebase since it's a library called foundation, but as you can see below
    // you can name it whatever you want! You'll just need to add it every now
    // and then. Anyways we need to implement these traits if we want to call
    // `try_execute` below.
    impl<'f> TryExecute for User<'f> {}
    impl<'f> TryExecute for Users<'f> {}
    impl<'a> TryExecute for UsersUsername<'a> {}

    // All the above work lets us chain functions. If you look at each of these
    // statements you can see we wired Get to User and Users through the
    // user() and users() function. We also added an extra function username()
    // that takes an input of a username for GitHub that only works if the
    // type is Users! You could also just call execute on these and deal with
    // them that way as well!
    client.get().user().try_execute();
    client.get().users().try_execute();
    client.get().users().username("mgattozzi").try_execute();

    // Some final bits of information. Get, Put, Post, Patch, and Delete all
    // implement the `set_body` method for the Request type (though slightly
    // tweaked) so you can set the payload needed for delivery. They also
    // implement `set_proxy`, and `set_version` if you need to change those
    // for any reason. If you need to manipulate these values in a Request do
    // that after calling methods like `get()` and before actually using them
    // like with a call to `user()` above.
}