// 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(bytes: &[u8]) -> Result 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::() { 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 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. }