33
44use std:: { marker:: PhantomData , time:: SystemTime } ;
55
6+ use generics:: {
7+ http:: { BuildError , HandleError , header:: HeaderValue , * } ,
8+ tokio_tungstenite:: tungstenite,
9+ ws:: { ContentEvent , ResponseOrContent , Topic , WsConfig , WsError , WsHandler } ,
10+ } ;
611use hmac:: { Hmac , Mac } ;
7- use rand:: { Rng , distributions :: Alphanumeric } ;
12+ use rand:: Rng ;
813use secrecy:: { ExposeSecret as _, SecretString } ;
914use serde:: { Deserialize , Serialize , de:: DeserializeOwned } ;
1015use serde_json:: json;
1116use sha2:: Sha256 ;
12- use v_exchanges_api_generics:: {
13- http:: { header:: HeaderValue , * } ,
14- websocket:: * ,
15- } ;
1617
1718use crate :: traits:: * ;
1819
1920/// The type returned by [Client::request()].
20- pub type BitFlyerRequestResult < T > = Result < T , BitFlyerRequestError > ;
21- pub type BitFlyerRequestError = RequestError < & ' static str , BitFlyerHandlerError > ;
21+ pub type BitFlyerRequestResult < T > = Result < T , RequestError > ;
2222
2323/// Options that can be set when creating handlers
2424pub enum BitFlyerOption {
@@ -121,24 +121,22 @@ where
121121 B : Serialize ,
122122 R : DeserializeOwned ,
123123{
124- type BuildError = & ' static str ;
125124 type Successful = R ;
126- type Unsuccessful = BitFlyerHandlerError ;
127125
128- fn base_url ( & self , is_test : bool ) -> String {
126+ fn base_url ( & self , is_test : bool ) -> Result < url :: Url , generics :: UrlError > {
129127 match is_test {
130128 true => unimplemented ! ( ) ,
131- false => self . options . http_url . as_str ( ) . to_owned ( ) ,
129+ false => url :: Url :: parse ( self . options . http_url . as_str ( ) ) . map_err ( generics :: UrlError :: Parse ) ,
132130 }
133131 }
134132
135- fn build_request ( & self , mut builder : RequestBuilder , request_body : & Option < B > , _: u8 ) -> Result < Request , Self :: BuildError > {
133+ fn build_request ( & self , mut builder : RequestBuilder , request_body : & Option < B > , _: u8 ) -> Result < Request , BuildError > {
136134 if let Some ( body) = request_body {
137- let json = serde_json:: to_vec ( body) . or ( Err ( "could not serialize body as application/json" ) ) ?;
135+ let json = serde_json:: to_vec ( body) . map_err ( BuildError :: JsonSerialization ) ?;
138136 builder = builder. header ( header:: CONTENT_TYPE , "application/json" ) . body ( json) ;
139137 }
140138
141- let mut request = builder. build ( ) . or ( Err ( "failed to build request" ) ) ?;
139+ let mut request = builder. build ( ) . map_err ( |e| BuildError :: Other ( eyre :: eyre! ( "failed to build request: {}" , e ) ) ) ?;
142140
143141 if self . options . http_auth {
144142 // https://lightning.bitflyer.com/docs?lang=en#authentication
@@ -154,13 +152,19 @@ where
154152
155153 let sign_contents = format ! ( "{}{}{}{}" , timestamp, request. method( ) , path, body) ;
156154
157- let secret = self . options . secret . as_ref ( ) . map ( |s| s. expose_secret ( ) ) . ok_or ( "API secret not set" ) ?;
155+ let secret = self
156+ . options
157+ . secret
158+ . as_ref ( )
159+ . map ( |s| s. expose_secret ( ) )
160+ . ok_or ( BuildError :: Auth ( generics:: AuthError :: MissingSecret ) ) ?;
158161 let mut hmac = Hmac :: < Sha256 > :: new_from_slice ( secret. as_bytes ( ) ) . unwrap ( ) ; // hmac accepts key of any length
159162
160163 hmac. update ( sign_contents. as_bytes ( ) ) ;
161164 let signature = hex:: encode ( hmac. finalize ( ) . into_bytes ( ) ) ;
162165
163- let key = HeaderValue :: from_str ( self . options . key . as_deref ( ) . ok_or ( "API key not set" ) ?) . or ( Err ( "invalid character in API key" ) ) ?;
166+ let key = HeaderValue :: from_str ( self . options . key . as_deref ( ) . ok_or ( BuildError :: Auth ( generics:: AuthError :: MissingPubkey ) ) ?)
167+ . map_err ( |e| BuildError :: Auth ( generics:: AuthError :: InvalidCharacterInApiKey ( e. to_string ( ) ) ) ) ?;
164168 let headers = request. headers_mut ( ) ;
165169 headers. insert ( "ACCESS-KEY" , key) ;
166170 headers. insert ( "ACCESS-TIMESTAMP" , HeaderValue :: from ( timestamp) ) ;
@@ -171,127 +175,27 @@ where
171175 Ok ( request)
172176 }
173177
174- fn handle_response ( & self , status : StatusCode , _: HeaderMap , response_body : Bytes ) -> Result < Self :: Successful , Self :: Unsuccessful > {
178+ fn handle_response ( & self , status : StatusCode , _: HeaderMap , response_body : Bytes ) -> Result < Self :: Successful , HandleError > {
175179 if status. is_success ( ) {
176180 serde_json:: from_slice ( & response_body) . map_err ( |error| {
177181 tracing:: debug!( "Failed to parse response due to an error: {}" , error) ;
178- BitFlyerHandlerError :: ParseError
182+ HandleError :: ParseJson ( error )
179183 } )
180184 } else {
181185 let error = match serde_json:: from_slice ( & response_body) {
182- Ok ( parsed_error) => BitFlyerHandlerError :: ApiError ( parsed_error) ,
186+ Ok ( parsed_error) => HandleError :: Api ( generics :: http :: ApiError { status , body : parsed_error } ) ,
183187 Err ( error) => {
184188 tracing:: debug!( "Failed to parse error response due to an error: {}" , error) ;
185- BitFlyerHandlerError :: ParseError
189+ HandleError :: ParseJson ( error )
186190 }
187191 } ;
188192 Err ( error)
189193 }
190194 }
191195}
192196
193- impl WebSocketHandler for BitFlyerWebSocketHandler {
194- fn websocket_config ( & self ) -> WebSocketConfig {
195- let mut config = self . options . websocket_config . clone ( ) ;
196- if self . options . websocket_url != BitFlyerWebSocketUrl :: None {
197- config. url_prefix = self . options . websocket_url . as_str ( ) . to_owned ( ) ;
198- }
199- config
200- }
201-
202- fn handle_start ( & mut self ) -> Vec < WebSocketMessage > {
203- if self . options . websocket_auth {
204- // https://bf-lightning-api.readme.io/docs/realtime-api-auth
205- if let Some ( key) = self . options . key . as_deref ( ) {
206- if let Some ( secret) = self . options . secret . as_ref ( ) . map ( |s| s. expose_secret ( ) ) {
207- let time = SystemTime :: now ( ) . duration_since ( SystemTime :: UNIX_EPOCH ) . unwrap ( ) ; // always after the epoch
208- let timestamp = time. as_millis ( ) as u64 ;
209- let nonce: String = rand:: thread_rng ( ) . sample_iter ( & Alphanumeric ) . take ( 16 ) . map ( char:: from) . collect ( ) ;
210-
211- let mut hmac = Hmac :: < Sha256 > :: new_from_slice ( secret. as_bytes ( ) ) . unwrap ( ) ; // hmac accepts key of any length
212-
213- hmac. update ( format ! ( "{timestamp}{nonce}" ) . as_bytes ( ) ) ;
214- let signature = hex:: encode ( hmac. finalize ( ) . into_bytes ( ) ) ;
215-
216- let id = format ! ( "_auth{}" , time. as_nanos( ) ) ;
217- self . auth_id = Some ( id. clone ( ) ) ;
218-
219- return vec ! [ WebSocketMessage :: Text (
220- json!( {
221- "method" : "auth" ,
222- "params" : {
223- "api_key" : key,
224- "timestamp" : timestamp,
225- "nonce" : nonce,
226- "signature" : signature,
227- } ,
228- "id" : id,
229- } )
230- . to_string( ) ,
231- ) ] ;
232- } else {
233- tracing:: debug!( "API secret not set." ) ;
234- } ;
235- } else {
236- tracing:: debug!( "API key not set." ) ;
237- } ;
238- }
239- self . message_subscribe ( )
240- }
241-
242- fn handle_message ( & mut self , message : WebSocketMessage ) -> Vec < WebSocketMessage > {
243- #[ derive( Deserialize ) ]
244- struct Message {
245- #[ allow( dead_code) ]
246- jsonrpc : String , // 2.0
247- method : Option < String > ,
248- result : Option < serde_json:: Value > ,
249- params : Option < BitFlyerChannelMessage > ,
250- id : Option < String > ,
251- }
252-
253- match message {
254- WebSocketMessage :: Text ( message) => {
255- let message: Message = match serde_json:: from_str ( & message) {
256- Ok ( message) => message,
257- Err ( _) => {
258- tracing:: debug!( "Invalid JSON-RPC message received" ) ;
259- return vec ! [ ] ;
260- }
261- } ;
262- if self . options . websocket_auth && self . auth_id == message. id {
263- // result of auth
264- if message. result == Some ( serde_json:: Value :: Bool ( true ) ) {
265- tracing:: debug!( "WebSocket authentication successful" ) ;
266- return self . message_subscribe ( ) ;
267- } else {
268- tracing:: error!( "WebSocket authentication unsuccessful" ) ;
269- }
270- self . auth_id = None ;
271- } else if message. method . as_deref ( ) == Some ( "channelMessage" ) {
272- if let Some ( channel_message) = message. params {
273- ( self . message_handler ) ( channel_message) ;
274- }
275- }
276- }
277- WebSocketMessage :: Binary ( _) => tracing:: debug!( "Unexpected binary message received" ) ,
278- WebSocketMessage :: Ping ( _) | WebSocketMessage :: Pong ( _) => ( ) ,
279- }
280- vec ! [ ]
281- }
282- }
283-
284- impl BitFlyerWebSocketHandler {
285- #[ inline]
286- fn message_subscribe ( & self ) -> Vec < WebSocketMessage > {
287- self . options
288- . websocket_channels
289- . clone ( )
290- . into_iter ( )
291- . map ( |channel| WebSocketMessage :: Text ( json ! ( { "method" : "subscribe" , "params" : { "channel" : channel } } ) . to_string ( ) ) )
292- . collect ( )
293- }
294- }
197+ // TODO: Implement WsHandler for BitFlyerWebSocketHandler
198+ // The WebSocket implementation needs to be updated to match the new WsHandler trait
295199
296200impl BitFlyerHttpUrl {
297201 /// The base URL that this variant represents.
@@ -366,17 +270,8 @@ where
366270 }
367271}
368272
369- impl < H : FnMut ( BitFlyerChannelMessage ) + Send + ' static > WebSocketOption < H > for BitFlyerOption {
370- type WebSocketHandler = BitFlyerWebSocketHandler ;
371-
372- fn websocket_handler ( handler : H , options : Self :: Options ) -> Self :: WebSocketHandler {
373- BitFlyerWebSocketHandler {
374- message_handler : Box :: new ( handler) ,
375- auth_id : None ,
376- options,
377- }
378- }
379- }
273+ // TODO: Implement WsOption for BitFlyerOption
274+ // This needs to be updated to match the new WsOption trait
380275
381276impl HandlerOption for BitFlyerOption {
382277 type Options = BitFlyerOptions ;
0 commit comments