├── .gitignore ├── src ├── lib.rs ├── client.rs ├── http.rs ├── response.rs ├── activity │ ├── events.rs │ └── mod.rs └── error.rs ├── Cargo.toml ├── LICENSE ├── README.md └── examples └── public.rs /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled files 2 | *.o 3 | *.so 4 | *.rlib 5 | *.dll 6 | 7 | # Executables 8 | *.exe 9 | 10 | # Generated by Cargo 11 | /target/ 12 | *.lock 13 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate curl; 2 | extern crate chrono; 3 | extern crate rustc_serialize; 4 | 5 | pub mod response; 6 | pub mod client; 7 | pub mod error; 8 | pub mod http; 9 | 10 | pub use client::*; 11 | 12 | pub mod activity; 13 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | 3 | name = "github" 4 | version = "0.1.2" 5 | description = "A Rust GitHub v3 API client." 6 | readme = "README.md" 7 | documentation = "http://glendc.github.io/github-rust/github/index.html" 8 | repository = "https://github.com/GlenDC/github-rust" 9 | license = "MIT" 10 | authors = ["glendc "] 11 | keywords = ["github", "api", "v3"] 12 | 13 | [dependencies] 14 | curl = "0.2" 15 | rustc-serialize = "^0.3" 16 | chrono = "0.2" 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Glen De Cauwsemaecker 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rust's GitHub v3 API 2 | 3 | A Rust library to communicate with GitHub via the official v3 REST API. 4 | 5 | ## [Documentation](http://glendc.github.io/github-rust/github/index.html) 6 | 7 | You can find the automaticly generated docs for the last published version [here](http://glendc.github.io/github-rust/github/index.html). 8 | 9 | ## Issues 10 | 11 | Feel free to submit issues and enhancement requests. 12 | 13 | ## Contributing 14 | 15 | I follow the _fork-and-pull_ git workflow for code contributions: 16 | 17 | 1. Fork the repo on GitHub 18 | 1. Warn me by mail or by corresponding on the relevant issue to indicate what you're working on 19 | 1. Commit changes to a branch in your fork 20 | 1. Make sure to pull the latest changes (rebase when possible) if not done yet! 21 | 1. If you have conflicts, you should merge them, but try to prevent where possible! 22 | 1. Pull request "upstream" with your changes 23 | 24 | You can contribute by improving code, resolve an open issue or add a new feature. In case you can't help with code due to skill-, time- or energy-limitations, but you do have suggestions or errors to report, than feel free to open an issue. 25 | 26 | Still not sure how you can contribute? [Contact me ](mailto:contact@glendc.com) and we'll find something :) 27 | 28 | ## License 29 | 30 | This repository and all its content falls under [the MIT license](./LICENSE). 31 | -------------------------------------------------------------------------------- /src/client.rs: -------------------------------------------------------------------------------- 1 | /// By default and in most scenarios, `DEFAULT_BASE_URL` 2 | /// will be the base url for requests via this Client library. 3 | static DEFAULT_BASE_URL: &'static str = "https://api.github.com/"; 4 | /// By default and in most scenarios, `DEFAULT_BASE_URL` 5 | /// will be the base upload url for requests via this Client library. 6 | static DEFAULT_UPLOAD_BASE_URL: &'static str = "https://uploads.github.com/"; 7 | 8 | /// The `Client` struct represent the user agent and base URLs. 9 | /// Functions in this library will never mutate a `Client` object 10 | /// and for th sake of parallel processing, you should try to keep it immutable. 11 | pub struct Client { 12 | /// `user_agent` represents the value given 13 | /// under the User-Agent key as part of 14 | /// the header of each request. 15 | pub user_agent: String, 16 | /// The base url for non-upload requests. 17 | pub base_url: String, 18 | /// The base url for upload requests. 19 | pub upload_url: String, 20 | } 21 | 22 | impl Client { 23 | /// Construct a `Client` for a custom domain, other than GitHub. 24 | pub fn custom(user: &str, base_url: &str, upload_url: &str) -> Client { 25 | Client { 26 | user_agent: user.to_string(), 27 | base_url: base_url.to_string(), 28 | upload_url: upload_url.to_string(), 29 | } 30 | } 31 | 32 | /// Construct a `Client` using the default URLs as defined by GitHub. 33 | pub fn new(user: &str) -> Client { 34 | Client::custom(user, DEFAULT_BASE_URL, DEFAULT_UPLOAD_BASE_URL) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/http.rs: -------------------------------------------------------------------------------- 1 | use ::response::*; 2 | use ::error::*; 3 | 4 | use std::str; 5 | 6 | use curl::http as curl_http; 7 | 8 | use rustc_serialize::json; 9 | use rustc_serialize::Decodable; 10 | 11 | /// The `API_ACCEPT_HEADER` value is specified under the Accept header, 12 | /// to enforce the use of the supported GitHub API, which is version 3. 13 | static API_ACCEPT_HEADER: &'static str = "application/vnd.github.v3+json"; 14 | 15 | /// A simplistic function that wraps around the behaviour of an 16 | /// http get-request as defined in `curl`. 17 | /// As the library gets more complete, a more complete and complex 18 | /// approach might be needed. 19 | pub fn get(user: &str, url: &str, opts: Option>) -> Result<(Vec, Response), ClientError> { 20 | // Creating an empty request with header info needed for all requests. 21 | let mut handle = curl_http::handle(); 22 | let mut request = handle.get(url) 23 | .header("User-Agent", user).header("Accept", API_ACCEPT_HEADER); 24 | 25 | // In case extre header options are needed, 26 | // it can be defined and given via the `opts` parameter. 27 | if opts.is_some() { 28 | for (name, val) in opts.unwrap() { 29 | request = request.header(name, val); 30 | } 31 | } 32 | 33 | // Executing the actual request via curl and storing the response. 34 | let response = request.exec().unwrap(); 35 | // Retrieving the status code from the response object. 36 | let status_code = response.get_code(); 37 | 38 | // Decoding the header and body in a controlled fashion, 39 | // throwing an error in case something went wrong internally, 40 | // replacing a panic, or when a response was negative. 41 | if !check_status_code(status_code) { 42 | return RequestError::new(status_code, response.get_body()); 43 | } 44 | let raw_body = match str::from_utf8(response.get_body()) { 45 | Ok(raw_body) => raw_body, 46 | Err(e) => return InternalError::new(&format!("{}", e)), 47 | }; 48 | match json::decode(raw_body) { 49 | Ok(b) => { 50 | let body: Vec = b; 51 | Ok((body, Response::populate(response.get_headers()))) 52 | }, 53 | Err(e) => InternalError::new(&format!("{}", e)), 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /examples/public.rs: -------------------------------------------------------------------------------- 1 | #![deny(warnings)] 2 | 3 | extern crate github; 4 | 5 | // Example of some public requests 6 | 7 | use github::Client; 8 | use github::error::*; 9 | use github::activity::events::*; 10 | 11 | fn main() { 12 | // simplest and most-used way of creating a client 13 | // requiring a User-Agent used for requests. 14 | let client = &Client::new("glendc"); 15 | 16 | // An example of getting and quickly summarizing the most recent public github events. 17 | // In this case we simply print all error information found. 18 | println!("# Example: list_events"); 19 | match list_events(client) { 20 | Ok((events, resp)) => { 21 | println!("listing public events succesfull, we have {} requsts remaining of {}. Limit resets @ {}...", 22 | resp.rate.remaining, resp.rate.limit, resp.rate.reset); 23 | for event in events { 24 | println!("event #{} at {} by {}...", 25 | event.id, event.created_at, event.actor.login); 26 | } 27 | } 28 | Err(err) => { 29 | println!("list_events => {}", err); 30 | if let ClientError::Http(http_error) = err { 31 | for error in http_error.errors { 32 | println!(" {}", error); 33 | } 34 | } 35 | } 36 | } 37 | 38 | // An example of a request that we expect to fail, 39 | // because the repository doesn't exist (404). 40 | println!("# Example: failed list_my_repo_events"); 41 | if let Err(err) = list_my_repo_events(client, "42") { 42 | println!("list_repo_events failed: {}", err); 43 | } 44 | 45 | // An example of a request that we expect to fail, 46 | // because we are unauthorized (404). 47 | println!("# Example: failed list_organisation_events"); 48 | if let Err(err) = list_my_organisation_events(client, "PortalGaming") { 49 | println!("list_organisation_events failed: {}", err); 50 | } 51 | 52 | // An example of getting and quickly summarizing 53 | // the most recent public issue events of the repo of this Client Library. 54 | // Most structs and enums in this lib are also debug-able, as shown here. 55 | println!("# Example: list_my_repo_issue_events for `github-rust`"); 56 | if let Ok((events, resp)) = list_my_repo_issue_events(client, "github-rust") { 57 | println!("Debug info => {:?}", resp); 58 | for event in events { 59 | println!("event ({}) #{} at {} by {}...", 60 | event.event, event.id, event.created_at, event.actor.login); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/response.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::default::Default; 3 | use std::str::FromStr; 4 | 5 | /// `HttpHeaderType` defines the type used for raw http headers 6 | pub type HttpHeaderType = HashMap>; 7 | 8 | /// `Populatable` provides functionality to construct 9 | /// an object based on info found in the http response header 10 | pub trait Populatable { 11 | /// `populate` constructs a new object based on the 12 | /// information found in the http response header 13 | fn populate(raw_header: &HttpHeaderType) -> Self; 14 | } 15 | 16 | /// `Rate` represents the X-Rate-Limit data 17 | /// provided by the Github v3 API and provided for each response 18 | #[derive(Debug)] 19 | pub struct Rate { 20 | /// the maximum limit of requests 21 | pub limit: u32, 22 | /// remaining requests possible 23 | pub remaining: u32, 24 | /// the date when this limit resets 25 | /// TODO: replace with proper DateTime 26 | pub reset: String 27 | } 28 | 29 | /// `Page` represents a link related to the response 30 | #[derive(Debug)] 31 | pub struct Page { 32 | /// the actual page number 33 | pub number: u64, 34 | } 35 | 36 | /// TODO: implement functionality 37 | 38 | /// `Response` represents the exposed data given with each 39 | /// request and populated by the Github v3 API 40 | #[derive(Debug)] 41 | pub struct Response { 42 | /// the raw response header 43 | pub resp: HttpHeaderType, 44 | /// the immediate next page of result 45 | pub next: Option, 46 | /// the last page of results 47 | pub last: Option, 48 | /// the first page of results 49 | pub first: Option, 50 | /// the immediate previous page of results 51 | pub prev: Option, 52 | /// the latest X-Rate-Limit info 53 | pub rate: Rate, 54 | } 55 | 56 | /// Get a single raw header value for type `T` 57 | /// using its default value when str::parse failed 58 | fn get_single_header_value(raw_data: &HttpHeaderType, key: &str) -> T where T: Default + FromStr { 59 | match str::parse(&raw_data[key][0]) { 60 | Ok(x) => x, 61 | Err(..) => Default::default(), 62 | } 63 | } 64 | 65 | impl Populatable for Rate { 66 | /// `populate` a `Rate` object from the HTTP response header 67 | fn populate(raw_header: &HttpHeaderType) -> Rate { 68 | Rate { 69 | limit: get_single_header_value(raw_header, "x-ratelimit-limit"), 70 | remaining: get_single_header_value(raw_header, "x-ratelimit-remaining"), 71 | reset: raw_header["x-ratelimit-reset"][0].clone(), 72 | } 73 | } 74 | } 75 | 76 | impl Populatable for Response { 77 | /// `populate` a `Response` object from the HTTP response header 78 | /// TODO: populate pages properly 79 | fn populate(raw_header: &HttpHeaderType) -> Response { 80 | Response { 81 | next: None, 82 | last: None, 83 | first: None, 84 | prev: None, 85 | rate: Rate::populate(raw_header), 86 | resp: raw_header.clone(), 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/activity/events.rs: -------------------------------------------------------------------------------- 1 | use Client; 2 | 3 | use activity::EventReturnType; 4 | use activity::IssueEventReturnType; 5 | 6 | /// Documentation References: 7 | /// https://developer.github.com/v3/activity/events/ 8 | 9 | /// List public events. 10 | pub fn list_events(client: &Client) -> EventReturnType { 11 | ::http::get( 12 | &client.user_agent, 13 | &format!("{}events", client.base_url), 14 | None) 15 | } 16 | 17 | /// List repository events. 18 | pub fn list_repo_events(client: &Client, user: &str, repo: &str) -> EventReturnType { 19 | ::http::get( 20 | &client.user_agent, 21 | &format!("{}repos/{}/{}/events", client.base_url, user, repo), 22 | None) 23 | } 24 | 25 | /// List repository events for a repository from 26 | /// the user defined in `Client` as `user_agent`. 27 | pub fn list_my_repo_events(client: &Client, repo: &str) -> EventReturnType { 28 | list_repo_events(client, &client.user_agent, repo) 29 | } 30 | 31 | /// List events that a user has received. 32 | /// 33 | /// These are events that you’ve received by watching repos and following users. 34 | /// If you are authenticated as the given user, you will see private events. 35 | /// Otherwise, you’ll only see public events. 36 | pub fn list_received_user_events(client: &Client, user: &str) -> EventReturnType { 37 | ::http::get( 38 | &client.user_agent, 39 | &format!("{}users/{}/received_events", client.base_url, user), 40 | None) 41 | } 42 | 43 | /// List events that the user, defined in `Client` as `user_agent`, received. 44 | pub fn list_my_received_events(client: &Client) -> EventReturnType { 45 | list_received_user_events(client, &client.user_agent) 46 | } 47 | 48 | /// List public events that a user has received. 49 | pub fn list_received_public_user_events(client: &Client, user: &str) -> EventReturnType { 50 | ::http::get( 51 | &client.user_agent, 52 | &format!("{}users/{}/received_events/public", client.base_url, user), 53 | None) 54 | } 55 | 56 | /// List public events that the user, defined in `Client` as `user_agent`, received. 57 | pub fn list_my_received_public_events(client: &Client) -> EventReturnType { 58 | list_received_public_user_events(client, &client.user_agent) 59 | } 60 | 61 | /// List events performed by a user. 62 | /// 63 | /// If you are authenticated as the given user, you will see your private events. 64 | /// Otherwise, you’ll only see public events. 65 | pub fn list_user_events(client: &Client, user: &str) -> EventReturnType { 66 | ::http::get( 67 | &client.user_agent, 68 | &format!("{}users/{}/events", client.base_url, user), 69 | None) 70 | } 71 | 72 | /// List events performed by the user defined in `Client` as `user_agent`. 73 | pub fn list_my_events(client: &Client) -> EventReturnType { 74 | list_user_events(client, &client.user_agent) 75 | } 76 | 77 | /// List public events performed by a user. 78 | pub fn list_public_user_events(client: &Client, user: &str) -> EventReturnType { 79 | ::http::get( 80 | &client.user_agent, 81 | &format!("{}users/{}/events/public", client.base_url, user), 82 | None) 83 | } 84 | 85 | /// List public events performed by 86 | /// the user defined in the `Client` as `user_agent`. 87 | pub fn list_my_public_user_events(client: &Client) -> EventReturnType { 88 | list_public_user_events(client, &client.user_agent) 89 | } 90 | 91 | /// List public events for a network of repositories. 92 | pub fn list_public_network_repo_events(client: &Client, user: &str, repo: &str) -> EventReturnType { 93 | ::http::get( 94 | &client.user_agent, 95 | &format!("{}networks/{}/{}/events", client.base_url, user, repo), 96 | None) 97 | } 98 | 99 | /// List public events for a network of repositories from 100 | /// the owner defined in the `Client` as `user_agent`. 101 | pub fn list_my_public_network_repo_events(client: &Client, repo: &str) -> EventReturnType { 102 | list_public_network_repo_events(client, &client.user_agent, repo) 103 | } 104 | 105 | /// List public events for an organization. 106 | pub fn list_public_organisation_events(client: &Client, org: &str) -> EventReturnType { 107 | ::http::get( 108 | &client.user_agent, 109 | &format!("{}orgs/{}/events", client.base_url, org), 110 | None) 111 | } 112 | 113 | /// List events for an organization. 114 | /// 115 | /// This is the user’s organization dashboard. 116 | /// You must be authenticated as the user to view this. 117 | pub fn list_organisation_events(client: &Client, user: &str, org: &str) -> EventReturnType { 118 | ::http::get( 119 | &client.user_agent, 120 | &format!("{}users/{}/events/orgs/{}", client.base_url, user, org), 121 | None) 122 | } 123 | 124 | /// List events for an organization as 125 | /// the user defined in `Client` as `user_agent`. 126 | pub fn list_my_organisation_events(client: &Client, org: &str) -> EventReturnType { 127 | list_organisation_events(client, &client.user_agent, org) 128 | } 129 | 130 | /// List issue events for a repository. 131 | /// 132 | /// Repository issue events have a different format than other events, 133 | /// as documented by the GitHub Events API and represented by `IssueEventResponse`. 134 | pub fn list_repo_issue_events(client: &Client, user: &str, repo: &str) -> IssueEventReturnType { 135 | ::http::get( 136 | &client.user_agent, 137 | &format!("{}repos/{}/{}/issues/events", client.base_url, user, repo), 138 | None) 139 | } 140 | 141 | /// List issue events for a repository owned by 142 | /// the user defined in `Client` as `user_agent`. 143 | pub fn list_my_repo_issue_events(client: &Client, repo: &str) -> IssueEventReturnType { 144 | list_repo_issue_events(client, &client.user_agent, repo) 145 | } 146 | -------------------------------------------------------------------------------- /src/activity/mod.rs: -------------------------------------------------------------------------------- 1 | use error::*; 2 | use response::Response; 3 | 4 | use rustc_serialize::Decoder; 5 | use rustc_serialize::Decodable; 6 | 7 | use std::fmt; 8 | 9 | /// Documentation References: 10 | /// https://developer.github.com/v3/activity/ 11 | 12 | /// All Activity::Events have the same response format. 13 | /// The following structs represent this info found as a json response. 14 | 15 | /// `Repository` contains all info regarding a git repository. 16 | #[derive(Debug, RustcDecodable)] 17 | pub struct Repository { 18 | pub id: u64, 19 | pub name: String, 20 | pub url: String, 21 | } 22 | 23 | /// `Actor` contains all info on the user generating the event. 24 | #[derive(Debug, RustcDecodable)] 25 | pub struct Actor { 26 | pub id: u64, 27 | pub login: String, 28 | pub gravatar_id: String, 29 | pub avatar_url: String, 30 | pub url: String, 31 | pub html_url: Option, 32 | pub followers_url: Option, 33 | pub following_url: Option, 34 | pub gists_url: Option, 35 | pub starred_url: Option, 36 | pub subscriptions_url: Option, 37 | pub organizations_url: Option, 38 | pub repos_url: Option, 39 | pub events_url: Option, 40 | pub received_events_url: Option, 41 | pub site_admin: Option, 42 | } 43 | 44 | /// `Organisation` contains all info on the organisation related to the event. 45 | #[derive(Debug, RustcDecodable)] 46 | pub struct Organisation { 47 | pub id: u64, 48 | pub login: String, 49 | pub gravatar_id: String, 50 | pub avatar_url: String, 51 | pub url: String, 52 | } 53 | 54 | /// `EventResponse` represents the response for almost 55 | /// all event requests found in `activity::events` with issues as an exception. 56 | #[derive(Debug, RustcDecodable)] 57 | pub struct EventResponse { 58 | pub public: bool, 59 | pub repo: Repository, 60 | pub actor: Actor, 61 | pub org: Option, 62 | // todo: replace with proper time 63 | pub created_at: String, 64 | pub id: String, 65 | } 66 | 67 | /// `IssueEventType` is an enumuration of 68 | /// all the different types of Issue Events. 69 | #[derive(Debug)] 70 | pub enum IssueEventType { 71 | /// The issue was closed by the `Actor`. 72 | Closed, 73 | /// The issue was reopened by the `Actor`. 74 | Reopened, 75 | /// The `Actor` subscribed to receive notifications for an issue. 76 | Subscribed, 77 | /// The issue was merged by the `Actor`. 78 | Merged, 79 | /// The issue was referenced from a commit message. 80 | Referenced, 81 | /// The `Actor` was @mentioned in an issue body. 82 | Mentioned, 83 | /// The issue was assigned to the `Actor`. 84 | Assigned, 85 | /// The issue was unassigned to the `Actor`. 86 | Unassigned, 87 | /// A label was added to the issue. 88 | Labeled, 89 | /// A label was removed from the issue. 90 | Unlabeled, 91 | /// The issue was added to a milestone. 92 | Milestoned, 93 | /// The issue was removed from a milestone. 94 | Demilestoned, 95 | /// The issue title was changed. 96 | Renamed, 97 | /// The issue was locked by the `Actor`. 98 | Locked, 99 | /// The issue was unlocked by the `Actor`. 100 | Unlocked, 101 | /// The pull request’s branch was deleted. 102 | HeadRefDeleted, 103 | /// The pull request’s branch was restored. 104 | HeadRefRestored, 105 | /// `Unknown(String)` is used as a last resort when an event is unknown. 106 | /// This should never happen, please report/resolve the issue when it does happen. 107 | Unknown(String), 108 | } 109 | 110 | /// Allowing `IssueEventType` to be printed via `{}`. 111 | impl fmt::Display for IssueEventType { 112 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 113 | let msg: &str = match *self { 114 | IssueEventType::Closed => "closed", 115 | IssueEventType::Reopened => "reopened", 116 | IssueEventType::Subscribed => "subscribed", 117 | IssueEventType::Merged => "merged", 118 | IssueEventType::Referenced => "referenced", 119 | IssueEventType::Mentioned => "mentioned", 120 | IssueEventType::Assigned => "assigned", 121 | IssueEventType::Unassigned => "unassigned", 122 | IssueEventType::Labeled => "labeled", 123 | IssueEventType::Unlabeled => "unlabeled", 124 | IssueEventType::Milestoned => "milestoned", 125 | IssueEventType::Demilestoned => "demilestoned", 126 | IssueEventType::Renamed => "renamed", 127 | IssueEventType::Locked => "locked", 128 | IssueEventType::Unlocked => "unlocked", 129 | IssueEventType::HeadRefDeleted => "head reference deleted", 130 | IssueEventType::HeadRefRestored => "head reference restored", 131 | IssueEventType::Unknown(ref s) => &s, 132 | }; 133 | 134 | write!(f, "{}", msg) 135 | } 136 | } 137 | 138 | /// Allowing `IssueEventType` to be decoded from json values. 139 | /// Linked to the `event` key to the `IssueEventType` enumeration. 140 | impl Decodable for IssueEventType { 141 | fn decode(d: &mut D) -> Result { 142 | match d.read_str() { 143 | Ok(code) => Ok(match &*code { 144 | "closed" => IssueEventType::Closed, 145 | "reopened" => IssueEventType::Reopened, 146 | "subscribed" => IssueEventType::Subscribed, 147 | "merged" => IssueEventType::Merged, 148 | "referenced" => IssueEventType::Referenced, 149 | "mentioned" => IssueEventType::Mentioned, 150 | "assigned" => IssueEventType::Assigned, 151 | "unassigned" => IssueEventType::Unassigned, 152 | "labeled" => IssueEventType::Labeled, 153 | "unlabeled" => IssueEventType::Unlabeled, 154 | "milestoned" => IssueEventType::Milestoned, 155 | "demilestoned" => IssueEventType::Demilestoned, 156 | "renamed" => IssueEventType::Renamed, 157 | "locked" => IssueEventType::Locked, 158 | "unlocked" => IssueEventType::Unlocked, 159 | "head_ref_deleted" => IssueEventType::HeadRefDeleted, 160 | "head_ref_restored" => IssueEventType::HeadRefRestored, 161 | unknown => IssueEventType::Unknown(unknown.to_string()), 162 | }), 163 | Err(err) => Err(err), 164 | } 165 | } 166 | } 167 | 168 | /// `EventResponse` represents the response for 169 | /// all issue event requests found in `activity::events`. 170 | #[derive(Debug, RustcDecodable)] 171 | pub struct IssueEventResponse { 172 | pub public: bool, 173 | pub repo: Repository, 174 | pub actor: Actor, 175 | pub org: Option, 176 | pub event: IssueEventType, 177 | pub created_at: String, 178 | pub commit_id: String, 179 | pub id: String, 180 | } 181 | 182 | /// `EventReturnType` is the return type for most event-requests. 183 | pub type EventReturnType = Result<(Vec, Response), ClientError>; 184 | /// `EventReturnType` is the return type for issue-event-requests. 185 | pub type IssueEventReturnType = Result<(Vec, Response), ClientError>; 186 | 187 | pub mod events; 188 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use rustc_serialize::json; 2 | use rustc_serialize::Decoder; 3 | use rustc_serialize::Decodable; 4 | 5 | use std::str; 6 | use std::fmt; 7 | 8 | /// Documentation References: 9 | /// https://developer.github.com/v3/#client-errors 10 | 11 | /// `ErrorCode` represents the type of error that was reported 12 | /// as a response on a request to th Github API. 13 | #[derive(Debug)] 14 | pub enum ErrorCode { 15 | /// This means a resource does not exist. 16 | Missing, 17 | /// This means a required field on a resource has not been set. 18 | MissingField, 19 | /// This means the formatting of a field is invalid. 20 | /// The documentation for that resource should be able 21 | /// to give you more specific information. 22 | Invalid, 23 | /// This means another resource has the same value as this field. 24 | /// This can happen in resources that must 25 | /// have some unique key (such as Label names). 26 | AlreadyExists, 27 | /// `Unknown(String)` is used as a last resort when an error code is unknown. 28 | /// This should never happen, please report/resolve the issue when it does happen. 29 | Unknown(String), 30 | } 31 | 32 | /// Allowing `ErrorCode` to be printed via `{}`. 33 | impl fmt::Display for ErrorCode { 34 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 35 | let msg: &str = match *self { 36 | ErrorCode::Missing => "resource does not exist", 37 | ErrorCode::MissingField => "required field on the resource has not been set", 38 | ErrorCode::Invalid => "the formatting of the field is invalid", 39 | ErrorCode::AlreadyExists => "another resource has the same value as this field", 40 | ErrorCode::Unknown(ref s) => &s, 41 | }; 42 | 43 | write!(f, "{}", msg) 44 | } 45 | } 46 | 47 | /// Allowing `ErrorCode` to be decoded from json values. 48 | /// Linked to the `error` key as defind by the `ErrorContext` struct's member. 49 | impl Decodable for ErrorCode { 50 | fn decode(d: &mut D) -> Result { 51 | match d.read_str() { 52 | Ok(code) => Ok(match &*code { 53 | "missing" => ErrorCode::Missing, 54 | "missing_field" => ErrorCode::MissingField, 55 | "invalid" => ErrorCode::Invalid, 56 | "already_exists" => ErrorCode::AlreadyExists, 57 | unknown => ErrorCode::Unknown(unknown.to_string()), 58 | }), 59 | Err(err) => Err(err), 60 | } 61 | } 62 | } 63 | 64 | /// When a request was successful. 65 | const STATUS_OK: u32 = 200; 66 | /// There was a problem with the data sent with the request. 67 | const STATUS_BAD_REQUEST: u32 = 400; 68 | /// Given as a response to requests the user has insufficient permissions for. 69 | const STATUS_FORBIDDEN: u32 = 403; 70 | /// Given when the info requested is not found because it 71 | /// either doesn't exist or because you are not authorized. 72 | const STATUS_NOT_FOUND: u32 = 404; 73 | /// Given when a field or resource couldn't be processed properly. 74 | const STATUS_UNPROCCESSABLE_ENTITY: u32 = 422; 75 | 76 | /// When a negative status was given as a response to a request, 77 | /// there might be one or several error descriptions embedded in the 78 | /// body to tell more about the details of what was wrong. 79 | /// `ErrorContext` is the representation for each of the errors that are given. 80 | #[derive(RustcDecodable, Debug)] 81 | pub struct ErrorContext { 82 | pub resource: String, 83 | pub field: String, 84 | pub code: ErrorCode, 85 | } 86 | 87 | /// Allowing `ErrorContext` to be printed via `{}` in a controlled manner. 88 | impl fmt::Display for ErrorContext { 89 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 90 | write!(f, "Error found in {}.{}: {}", self.resource, self.field, self.code) 91 | } 92 | } 93 | 94 | /// `ErrorStatus` represents the status code given in the header of a negative response. 95 | /// Look at const definitions such as `STATUS_OK` for more information for each value. 96 | #[derive(Debug)] 97 | pub enum ErrorStatus{ 98 | BadRequest, 99 | UnprocessableEntity, 100 | Forbidden, 101 | NotFound, 102 | Unknown(u32), 103 | } 104 | 105 | /// Allowing `ErrorStatus` to be printed via `{}` in a controlled manner. 106 | impl fmt::Display for ErrorStatus { 107 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 108 | let (code, msg) = match *self { 109 | ErrorStatus::BadRequest => (STATUS_BAD_REQUEST, "Bad Request"), 110 | ErrorStatus::UnprocessableEntity => (STATUS_UNPROCCESSABLE_ENTITY, "Unprocessable Entity"), 111 | ErrorStatus::Forbidden => (STATUS_FORBIDDEN, "Forbidden Request"), 112 | ErrorStatus::NotFound => (STATUS_NOT_FOUND, "Not Found"), 113 | ErrorStatus::Unknown(e) => (e, "Unknown"), 114 | }; 115 | 116 | write!(f, "status {}: {}", code, msg) 117 | } 118 | } 119 | 120 | impl ErrorStatus { 121 | /// Simple way to construct an `ErrorStatus` 122 | /// based on its constant value as defined by the official docs. 123 | pub fn new(code: u32) -> ErrorStatus { 124 | match code { 125 | STATUS_BAD_REQUEST => ErrorStatus::BadRequest, 126 | STATUS_FORBIDDEN => ErrorStatus::Forbidden, 127 | STATUS_UNPROCCESSABLE_ENTITY => ErrorStatus::UnprocessableEntity, 128 | STATUS_NOT_FOUND => ErrorStatus::NotFound, 129 | unknown => ErrorStatus::Unknown(unknown), 130 | } 131 | } 132 | } 133 | 134 | /// `RequestError` will be returned as a `Result` in case 135 | /// a request responds negatively populated by information from 136 | /// both the header and body. 137 | #[derive(Debug)] 138 | pub struct RequestError { 139 | /// `code` represents the given status code 140 | /// stored in the form of `ErrorStatus`. 141 | pub code: ErrorStatus, 142 | /// In case detailed errors are available 143 | // they will be accessible via `errors`, stored as an `ErrorContext`. 144 | pub errors: Vec, 145 | } 146 | 147 | impl RequestError { 148 | /// Simple way to construct a `Result` based on 149 | /// the status code given in the header and the body in a raw utf8 buffer. 150 | pub fn new(code: u32, buffer: &[u8]) -> Result { 151 | Err(ClientError::Http(RequestError { 152 | code: ErrorStatus::new(code), 153 | errors: match str::from_utf8(buffer) { 154 | Err(..) => Vec::new(), 155 | Ok(body) => json::decode(body).unwrap_or(Vec::new()), 156 | }, 157 | })) 158 | } 159 | } 160 | 161 | /// Allowing `RequestError` to be printed via `{}` in a controlled manner. 162 | impl fmt::Display for RequestError { 163 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 164 | write!(f, "HTTP Error: {}. Found {} error description(s)!", self.code, self.errors.len()) 165 | } 166 | } 167 | 168 | /// `InternalError` will be given in the form of Result in 169 | /// case something went wrong within this Client Library. 170 | /// It replaces panics so that you can freely choose the behaviour. 171 | /// Please file an issue and/or resolve the bug yourself when you get this error. 172 | #[derive(Debug)] 173 | pub struct InternalError { 174 | /// `msg` is the actual description of the problem. 175 | /// future versions of this library might store extra info 176 | /// where it would help the debugging of an error. 177 | pub msg: String, 178 | } 179 | 180 | impl InternalError { 181 | /// Simple way to construct a `Result` based on 182 | /// information known for an internal error. 183 | pub fn new(msg: &str) -> Result { 184 | Err(ClientError::Internal(InternalError { msg: msg.to_string() })) 185 | } 186 | } 187 | 188 | /// Allowing `InternalError` to be printed via `{}` in a controlled manner. 189 | impl fmt::Display for InternalError { 190 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 191 | write!(f, "Internal Error: {}", self.msg) 192 | } 193 | } 194 | 195 | /// `ClientError` enumerates all the possible errors that a public 196 | /// client (request) function of this library might be given. 197 | #[derive(Debug)] 198 | pub enum ClientError { 199 | /// Read the documentation for `RequestError` 200 | /// for more information on this error. 201 | Http(RequestError), 202 | /// Read the documentation for `InternalError` 203 | /// for more information on this error.. 204 | Internal(InternalError) 205 | } 206 | 207 | /// Allowing `ClientError` to be printed via `{}` in a controlled manner. 208 | impl fmt::Display for ClientError { 209 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 210 | match self { 211 | &ClientError::Http(ref e) => write!(f, "{}", e), 212 | &ClientError::Internal(ref e) => write!(f, "{}", e), 213 | } 214 | } 215 | } 216 | 217 | /// Simplistic function internally used to check 218 | /// if a returned status code is positive. 219 | /// Which means that the request was succesful. 220 | pub fn check_status_code(code: u32) -> bool { 221 | code == STATUS_OK 222 | } 223 | --------------------------------------------------------------------------------