11extern crate clap;
2- extern crate hyper;
3- extern crate url;
42extern crate futures;
5- extern crate tokio_core ;
3+ extern crate hyper ;
64extern crate serde;
75extern crate serde_json;
8-
96#[ macro_use]
107extern crate serde_derive;
8+ extern crate tokio_core;
9+ extern crate url;
1110
1211use clap:: { Arg , ArgMatches , App } ;
12+ use futures:: { Future , Stream } ;
1313use hyper:: { Client , Method , Request , Uri } ;
14- use hyper:: header:: Authorization ;
1514use hyper:: header;
15+ use hyper:: header:: Authorization ;
16+ use std:: option:: Option :: * ;
1617use std:: str:: FromStr ;
17- use url:: { Url } ;
1818use tokio_core:: reactor:: Core ;
19- use futures :: { Future , Stream } ;
19+ use url :: { Url } ;
2020
2121fn main ( ) {
2222 match ClientConfig :: from_cli_args ( ) {
2323 Ok ( config) => {
2424 println ! ( "arguments are good: {:?}" , config) ;
25- make_test_request ( & config) ;
25+ let mut client = ApiClient :: new ( & config) . unwrap ( ) ;
26+ let request = client. get_project_list ( ) ;
27+ match client. run ( request) {
28+ Ok ( projects) => {
29+ for project in projects {
30+ println ! ( "{:?}" , project) ;
31+ }
32+ } ,
33+ Err ( e) => {
34+ println ! ( "Error in request: {:?}" , e) ;
35+ }
36+ }
2637 } ,
2738 Err ( ConfigError :: MissingAuth ) => println ! ( "Authorization info missing or incomplete. Either an API Key or a Username + Password must be provided" ) ,
2839 Err ( ConfigError :: MissingUrl ) => println ! ( "Missing the Base URL" ) ,
@@ -35,50 +46,114 @@ struct ApiProject {
3546 id : u32 ,
3647 name : String ,
3748 #[ serde( rename = "parentId" ) ]
38- parent_id : Option < u32 >
49+ parent_id : Option < u32 > ,
3950}
4051
41- fn make_test_request ( config : & ClientConfig ) {
42- let uri = config. api_uri ( & [ "x" , "projects" ] ) ;
52+ type BoxedClientFuture < T > = Box < ClientFuture < T > > ;
53+ type ClientFuture < T > = Future < Item = T , Error = ClientError > ;
54+ type ClientResult < T > = Result < T , ClientError > ;
4355
44- let mut req = Request :: new ( Method :: Get , uri) ;
45- config. auth_info . apply_to ( & mut req) ;
46- println ! ( "Request:\n {:?}" , req) ;
56+ struct ApiClient < ' a > {
57+ config : & ' a ClientConfig ,
58+ core : Core ,
59+ client : Client < hyper:: client:: HttpConnector , hyper:: Body >
60+ }
4761
48- let mut core = Core :: new ( )
49- . expect ( "Failed to create an event loop for making HTTP requests" ) ;
50- let client: Client < hyper:: client:: HttpConnector , hyper:: Body > = Client :: new ( & core. handle ( ) ) ;
62+ impl < ' a > ApiClient < ' a > {
63+ fn new ( config : & ' a ClientConfig ) -> std:: io:: Result < ApiClient > {
64+ Core :: new ( ) . map ( |core| {
65+ let client = Client :: new ( & core. handle ( ) ) ;
66+ ApiClient { config : & config, core, client }
67+ } )
68+ }
5169
52- let work = client. request ( req) . and_then ( |res| {
53- println ! ( "Response Status: {}" , res. status( ) ) ;
54- res. body ( )
55- . fold ( Vec :: new ( ) , |mut acc, chunk| {
56- acc. extend_from_slice ( & * chunk) ;
57- futures:: future:: ok :: < _ , hyper:: Error > ( acc)
58- } )
59- . map ( |body| {
60- let des: Vec < ApiProject > = serde_json:: from_slice ( & body) . unwrap ( ) ;
61- for project in des {
62- println ! ( "{:?}" , project) ;
63- }
64- } )
70+ pub fn run < T > ( & mut self , f : Box < ClientFuture < T > > ) -> ClientResult < T > {
71+ self . core . run ( f)
72+ }
73+
74+ fn get_project_list ( & self ) -> BoxedClientFuture < Vec < ApiProject > > {
75+ self . request_json ( Method :: Get , & [ "x" , "projects" ] )
76+ }
77+
78+ /// Underlying method for creating a request to be sent to Code Dx.
79+ ///
80+ /// Accepts a `method` (GET, POST, etc)
81+ /// and `path_segments` which form the API path, e.g. `&["x", "projects"]`.
82+ ///
83+ /// A type parameter `T` is given to indicate the concrete type the response
84+ /// should be deserialized to once the body is parsed as JSON.
85+ ///
86+ /// Returns a Boxed future that resolves with either the deserialized `T`
87+ /// or a `ClientError`.
88+ fn request_json < T > ( & self , method : Method , path_segments : & [ & str ] ) -> BoxedClientFuture < T >
89+ where T : serde:: de:: DeserializeOwned + std:: fmt:: Debug + ' static
90+ {
91+ self . request_json_with_body :: < T , String > ( method, path_segments, None )
92+ }
93+
94+ /// Underlying method for creating a request to be sent to Code Dx.
95+ ///
96+ /// Accepts a `method` (GET, POST, etc),
97+ /// `path_segments` which form the API path, e.g. `&["x", "projects"]`
98+ /// and an optional `body` which will be attached to the request.
99+ ///
100+ /// A type parameter `T` is given to indicate the concrete type the response
101+ /// should be deserialized to once the body is parsed as JSON.
102+ ///
103+ /// Returns a Boxed future that resolves with either the deserialized `T`
104+ /// or a `ClientError`.
105+ fn request_json_with_body < T , B : Into < hyper:: Body > > ( & self , method : Method , path_segments : & [ & str ] , body : Option < B > ) -> BoxedClientFuture < T >
106+ where T : serde:: de:: DeserializeOwned + std:: fmt:: Debug + ' static
107+ {
108+ let mut req = Request :: new ( method, self . config . api_uri ( path_segments) ) ;
109+ self . config . auth_info . apply_to ( & mut req) ;
110+ for b in body {
111+ req. set_body ( b) ;
112+ }
113+
114+ println ! ( "Request:\n {:?}" , req) ;
115+
116+ Box :: new (
117+ self . client . request ( req)
118+ . map_err ( |err| ClientError :: Protocol ( err) )
119+ . and_then ( |res| {
120+ match res. status ( ) {
121+ code if code. is_success ( ) => Ok ( res) ,
122+ code => Err ( ClientError :: NonSuccess ( code) )
123+ }
124+ } )
125+ . and_then ( |res| {
126+ res. body ( )
127+ . map_err ( |err| ClientError :: Protocol ( err) )
128+ . fold ( Vec :: new ( ) , |mut acc, chunk| {
129+ acc. extend_from_slice ( & * chunk) ;
130+ futures:: future:: ok :: < _ , ClientError > ( acc)
131+ } )
132+ . and_then ( |complete_body| {
133+ serde_json:: from_slice :: < T > ( & complete_body)
134+ . map_err ( |err| ClientError :: Json ( err) )
135+ } )
136+ } )
137+ )
138+ }
139+ }
140+
141+
142+
143+ /// Things that might go wrong when making a request with the client.
144+ #[ derive( Debug ) ]
145+ enum ClientError {
146+ /// Wrapper for errors in the underlying request, like invalid URI,
147+ /// request format, or IO issues while executing the request.
148+ Protocol ( hyper:: Error ) ,
65149
150+ /// Indicates that the request reached the server but the server
151+ /// responded with a non-success (non 2xx) response code.
152+ NonSuccess ( hyper:: StatusCode ) ,
66153
67- // let foo = res.body().concat().and_then(|body| {
68- //
69- // });
70- // let x = serde_json::from_reader(res.body());
71- // println!("JSON: {:?}", x);
72- // res.body().for_each(|chunk| {
73- // io::stdout()
74- // .write_all(&chunk)
75- // .map(|_| ())
76- // .map_err(From::from)
77- // })
78- } ) ;
79-
80- core. run ( work)
81- . expect ( "Failed to make request" ) ;
154+ /// Indicates that the response body was received but could not be
155+ /// parsed as JSON in the expected format.
156+ Json ( serde_json:: Error ) ,
82157}
83158
84159/// Connection information for Code Dx.
0 commit comments