@@ -12,16 +12,27 @@ mod surf;
12
12
pub struct HTTP < C : HttpClient > {
13
13
authorization_header : C :: HeaderName ,
14
14
app_name_header : C :: HeaderName ,
15
+ unleash_app_name_header : C :: HeaderName ,
16
+ unleash_sdk_header : C :: HeaderName ,
17
+ unleash_connection_id_header : C :: HeaderName ,
15
18
instance_id_header : C :: HeaderName ,
16
19
app_name : String ,
20
+ sdk_version : & ' static str ,
17
21
instance_id : String ,
22
+ // The connection_id represents a logical connection from the SDK to Unleash.
23
+ // It's assigned internally by the SDK and lives as long as the Unleash client instance.
24
+ // We can't reuse instance_id since some SDKs allow to override it while
25
+ // connection_id has to be uniquely defined by the SDK.
26
+ connection_id : String ,
18
27
authorization : Option < String > ,
19
28
client : C ,
20
29
}
21
30
31
+ use crate :: version:: get_sdk_version;
22
32
use serde:: { de:: DeserializeOwned , Serialize } ;
23
33
#[ doc( inline) ]
24
34
pub use shim:: HttpClient ;
35
+ use uuid:: Uuid ;
25
36
26
37
impl < C > HTTP < C >
27
38
where
@@ -36,10 +47,15 @@ where
36
47
Ok ( HTTP {
37
48
client : C :: default ( ) ,
38
49
app_name,
50
+ sdk_version : get_sdk_version ( ) ,
51
+ connection_id : Uuid :: new_v4 ( ) . to_string ( ) ,
39
52
instance_id,
40
53
authorization,
41
54
authorization_header : C :: build_header ( "authorization" ) ?,
42
55
app_name_header : C :: build_header ( "appname" ) ?,
56
+ unleash_app_name_header : C :: build_header ( "unleash-appname" ) ?,
57
+ unleash_sdk_header : C :: build_header ( "unleash-sdk" ) ?,
58
+ unleash_connection_id_header : C :: build_header ( "unleash-connection-id" ) ?,
43
59
instance_id_header : C :: build_header ( "instance_id" ) ?,
44
60
} )
45
61
}
73
89
74
90
fn attach_headers ( & self , request : C :: RequestBuilder ) -> C :: RequestBuilder {
75
91
let request = C :: header ( request, & self . app_name_header , self . app_name . as_str ( ) ) ;
92
+ let request = C :: header (
93
+ request,
94
+ & self . unleash_app_name_header ,
95
+ self . app_name . as_str ( ) ,
96
+ ) ;
97
+ let request = C :: header ( request, & self . unleash_sdk_header , self . sdk_version ) ;
98
+ let request = C :: header (
99
+ request,
100
+ & self . unleash_connection_id_header ,
101
+ self . connection_id . as_str ( ) ,
102
+ ) ;
76
103
let request = C :: header ( request, & self . instance_id_header , self . instance_id . as_str ( ) ) ;
77
104
if let Some ( auth) = & self . authorization {
78
105
C :: header ( request, & self . authorization_header . clone ( ) , auth. as_str ( ) )
@@ -81,3 +108,95 @@ where
81
108
}
82
109
}
83
110
}
111
+
112
+ #[ cfg( test) ]
113
+ mod tests {
114
+ use super :: * ;
115
+ use async_trait:: async_trait;
116
+ use regex:: Regex ;
117
+
118
+ #[ derive( Clone , Default ) ]
119
+ struct MockHttpClient {
120
+ headers : std:: collections:: HashMap < String , String > ,
121
+ }
122
+
123
+ #[ async_trait]
124
+ impl HttpClient for MockHttpClient {
125
+ type Error = std:: io:: Error ;
126
+ type HeaderName = String ;
127
+ type RequestBuilder = Self ;
128
+
129
+ fn build_header ( name : & ' static str ) -> Result < Self :: HeaderName , Self :: Error > {
130
+ Ok ( name. to_string ( ) )
131
+ }
132
+
133
+ fn header ( mut builder : Self , key : & Self :: HeaderName , value : & str ) -> Self :: RequestBuilder {
134
+ builder. headers . insert ( key. clone ( ) , value. to_string ( ) ) ;
135
+ builder
136
+ }
137
+
138
+ fn get ( & self , _uri : & str ) -> Self :: RequestBuilder {
139
+ self . clone ( )
140
+ }
141
+
142
+ fn post ( & self , _uri : & str ) -> Self :: RequestBuilder {
143
+ self . clone ( )
144
+ }
145
+
146
+ async fn get_json < T : DeserializeOwned > (
147
+ _req : Self :: RequestBuilder ,
148
+ ) -> Result < T , Self :: Error > {
149
+ unimplemented ! ( )
150
+ }
151
+
152
+ async fn post_json < T : Serialize + Sync > (
153
+ _req : Self :: RequestBuilder ,
154
+ _content : & T ,
155
+ ) -> Result < bool , Self :: Error > {
156
+ unimplemented ! ( )
157
+ }
158
+ }
159
+
160
+ #[ tokio:: test]
161
+ async fn test_specific_headers ( ) {
162
+ let http_client = HTTP :: < MockHttpClient > :: new (
163
+ "my_app" . to_string ( ) ,
164
+ "my_instance_id" . to_string ( ) ,
165
+ Some ( "auth_token" . to_string ( ) ) ,
166
+ )
167
+ . unwrap ( ) ;
168
+
169
+ let request_builder = http_client. client . post ( "http://example.com" ) ;
170
+ let request_with_headers = http_client. attach_headers ( request_builder) ;
171
+
172
+ assert_eq ! (
173
+ request_with_headers. headers. get( "unleash-appname" ) . unwrap( ) ,
174
+ "my_app"
175
+ ) ;
176
+ assert_eq ! (
177
+ request_with_headers. headers. get( "instance_id" ) . unwrap( ) ,
178
+ "my_instance_id"
179
+ ) ;
180
+ assert_eq ! (
181
+ request_with_headers. headers. get( "authorization" ) . unwrap( ) ,
182
+ "auth_token"
183
+ ) ;
184
+
185
+ let version_regex = Regex :: new ( r"^unleash-client-rust:\d+\.\d+\.\d+$" ) . unwrap ( ) ;
186
+ let sdk_version = request_with_headers. headers . get ( "unleash-sdk" ) . unwrap ( ) ;
187
+ assert ! (
188
+ version_regex. is_match( sdk_version) ,
189
+ "Version output did not match expected format: {}" ,
190
+ sdk_version
191
+ ) ;
192
+
193
+ let connection_id = request_with_headers
194
+ . headers
195
+ . get ( "unleash-connection-id" )
196
+ . unwrap ( ) ;
197
+ assert ! (
198
+ Uuid :: parse_str( connection_id) . is_ok( ) ,
199
+ "Connection ID is not a valid UUID"
200
+ ) ;
201
+ }
202
+ }
0 commit comments