11use reqwest:: StatusCode ;
2+ use reqwest:: blocking:: Body ;
3+ use reqwest:: header:: CONTENT_TYPE ;
24use serde:: Deserialize ;
35use std:: collections:: BTreeMap ;
46use std:: error;
57use std:: fmt;
8+ use std:: io:: Read ;
69
710const DEFAULT_TEXTBIN_URL : & str = "http://localhost:4000" ;
811
912#[ derive( Debug , Clone ) ]
1013pub struct Client {
1114 base_url : String ,
15+ http : reqwest:: blocking:: Client ,
1216}
1317
1418impl Client {
@@ -22,44 +26,104 @@ impl Client {
2226 pub fn new ( base_url : impl Into < String > ) -> Self {
2327 Self {
2428 base_url : base_url. into ( ) . trim_end_matches ( '/' ) . to_string ( ) ,
29+ http : reqwest:: blocking:: Client :: new ( ) ,
2530 }
2631 }
2732
2833 pub fn get_paste ( & self , id : & str ) -> Result < Paste , Error > {
2934 let url = format ! ( "{}/api/v1/pastes/{id}" , self . base_url) ;
30- let response = reqwest:: blocking:: get ( & url) . map_err ( |source| Error :: Request {
31- url : url. clone ( ) ,
32- source,
33- } ) ?;
34-
35- let status = response. status ( ) ;
36- let body = response. text ( ) . map_err ( |source| Error :: ReadResponse {
37- url : url. clone ( ) ,
38- source,
39- } ) ?;
40-
41- if !status. is_success ( ) {
42- return Err ( Error :: Api ( format_api_error ( status, & body) ) ) ;
43- }
35+ let response = self
36+ . http
37+ . get ( & url)
38+ . send ( )
39+ . map_err ( |source| Error :: Request {
40+ url : url. clone ( ) ,
41+ source,
42+ } ) ?;
4443
45- let response = serde_json:: from_str :: < ShowResponse > ( & body)
46- . map_err ( |source| Error :: Decode { source } ) ?;
44+ let response = decode_response :: < ShowResponse > ( & url, response) ?;
4745
4846 Ok ( response. data )
4947 }
48+
49+ pub fn create_paste (
50+ & self ,
51+ data : String ,
52+ syntax_highlight : Option < & str > ,
53+ ) -> Result < CreatedPaste , Error > {
54+ self . create_paste_body ( Body :: from ( data) , syntax_highlight)
55+ }
56+
57+ pub fn create_paste_stream < R > (
58+ & self ,
59+ reader : R ,
60+ syntax_highlight : Option < & str > ,
61+ ) -> Result < CreatedPaste , Error >
62+ where
63+ R : Read + Send + ' static ,
64+ {
65+ self . create_paste_body ( Body :: new ( reader) , syntax_highlight)
66+ }
67+
68+ fn create_paste_body (
69+ & self ,
70+ body : Body ,
71+ syntax_highlight : Option < & str > ,
72+ ) -> Result < CreatedPaste , Error > {
73+ let url = self . create_paste_url ( syntax_highlight) ;
74+ let response = self
75+ . http
76+ . post ( & url)
77+ . header ( CONTENT_TYPE , "text/plain" )
78+ . body ( body)
79+ . send ( )
80+ . map_err ( |source| Error :: Request {
81+ url : url. clone ( ) ,
82+ source,
83+ } ) ?;
84+
85+ let response = decode_response :: < CreateResponse > ( & url, response) ?;
86+
87+ Ok ( response. data )
88+ }
89+
90+ fn create_paste_url ( & self , syntax_highlight : Option < & str > ) -> String {
91+ let url = format ! ( "{}/api/v1/pastes" , self . base_url) ;
92+
93+ match syntax_highlight. filter ( |syntax| !syntax. is_empty ( ) ) {
94+ Some ( syntax_highlight) => {
95+ let mut url = reqwest:: Url :: parse ( & url) . expect ( "client base_url must be valid URL" ) ;
96+ url. query_pairs_mut ( )
97+ . append_pair ( "syntax_highlight" , syntax_highlight) ;
98+ url. into ( )
99+ }
100+ None => url,
101+ }
102+ }
50103}
51104
52105#[ derive( Debug , Deserialize ) ]
53106struct ShowResponse {
54107 data : Paste ,
55108}
56109
110+ #[ derive( Debug , Deserialize ) ]
111+ struct CreateResponse {
112+ data : CreatedPaste ,
113+ }
114+
57115#[ derive( Debug , Clone , Deserialize ) ]
58116pub struct Paste {
59117 pub data : String ,
60118 pub syntax_highlight : String ,
61119}
62120
121+ #[ derive( Debug , Clone , Deserialize ) ]
122+ pub struct CreatedPaste {
123+ pub id : String ,
124+ pub syntax_highlight : String ,
125+ }
126+
63127#[ derive( Debug , Deserialize ) ]
64128struct ApiErrorResponse {
65129 errors : ApiErrors ,
@@ -103,6 +167,30 @@ impl error::Error for Error {
103167 }
104168}
105169
170+ fn decode_response < T > ( url : & str , response : reqwest:: blocking:: Response ) -> Result < T , Error >
171+ where
172+ T : for < ' de > Deserialize < ' de > ,
173+ {
174+ let status = response. status ( ) ;
175+ let body = response. text ( ) . map_err ( |source| Error :: ReadResponse {
176+ url : url. to_string ( ) ,
177+ source,
178+ } ) ?;
179+
180+ if !status. is_success ( ) {
181+ return Err ( Error :: Api ( format_api_error ( status, & body) ) ) ;
182+ }
183+
184+ decode_json ( & body)
185+ }
186+
187+ fn decode_json < T > ( body : & str ) -> Result < T , Error >
188+ where
189+ T : for < ' de > Deserialize < ' de > ,
190+ {
191+ serde_json:: from_str ( body) . map_err ( |source| Error :: Decode { source } )
192+ }
193+
106194fn format_api_error ( status : StatusCode , body : & str ) -> String {
107195 let detail = serde_json:: from_str :: < ApiErrorResponse > ( body)
108196 . ok ( )
@@ -141,6 +229,30 @@ mod tests {
141229 assert_eq ! ( client. base_url, "http://localhost:4000" ) ;
142230 }
143231
232+ #[ test]
233+ fn create_paste_url_omits_empty_syntax_highlight ( ) {
234+ let client = Client :: new ( "http://localhost:4000/" ) ;
235+
236+ assert_eq ! (
237+ client. create_paste_url( None ) ,
238+ "http://localhost:4000/api/v1/pastes"
239+ ) ;
240+ assert_eq ! (
241+ client. create_paste_url( Some ( "" ) ) ,
242+ "http://localhost:4000/api/v1/pastes"
243+ ) ;
244+ }
245+
246+ #[ test]
247+ fn create_paste_url_adds_syntax_highlight_query_param ( ) {
248+ let client = Client :: new ( "http://localhost:4000/" ) ;
249+
250+ assert_eq ! (
251+ client. create_paste_url( Some ( "rust" ) ) ,
252+ "http://localhost:4000/api/v1/pastes?syntax_highlight=rust"
253+ ) ;
254+ }
255+
144256 #[ test]
145257 fn format_api_error_uses_detail_from_json_response ( ) {
146258 let message = format_api_error (
@@ -170,4 +282,15 @@ mod tests {
170282
171283 assert_eq ! ( message, "paste request failed: 500 Internal Server Error" ) ;
172284 }
285+
286+ #[ test]
287+ fn decodes_create_response_metadata ( ) {
288+ let response = decode_json :: < CreateResponse > (
289+ r#"{"data":{"id":"00000000-0000-0000-0000-000000000000","syntax_highlight":"plain"}}"# ,
290+ )
291+ . unwrap ( ) ;
292+
293+ assert_eq ! ( response. data. id, "00000000-0000-0000-0000-000000000000" ) ;
294+ assert_eq ! ( response. data. syntax_highlight, "plain" ) ;
295+ }
173296}
0 commit comments