1
1
//! Implements a Blossom client for interacting with Blossom servers
2
2
3
- use std:: error:: Error ;
4
3
use std:: time:: Duration ;
5
4
5
+ use base64:: engine:: general_purpose;
6
6
use base64:: Engine ;
7
7
use nostr:: hashes:: sha256:: Hash as Sha256Hash ;
8
8
use nostr:: hashes:: Hash ;
9
9
use nostr:: signer:: NostrSigner ;
10
- use nostr:: { EventBuilder , PublicKey , Timestamp } ;
10
+ use nostr:: { Event , EventBuilder , PublicKey , Timestamp } ;
11
11
use reqwest:: header:: { HeaderMap , HeaderValue , AUTHORIZATION , CONTENT_TYPE , RANGE } ;
12
12
#[ cfg( not( target_arch = "wasm32" ) ) ]
13
13
use reqwest:: redirect:: Policy ;
14
- use reqwest:: StatusCode ;
14
+ use reqwest:: { Response , StatusCode } ;
15
15
16
16
use crate :: bud01:: {
17
17
BlossomAuthorization , BlossomAuthorizationScope , BlossomAuthorizationVerb ,
18
18
BlossomBuilderExtension ,
19
19
} ;
20
20
use crate :: bud02:: BlobDescriptor ;
21
+ use crate :: error:: Error ;
21
22
22
23
/// A client for interacting with a Blossom server
23
24
///
@@ -57,7 +58,7 @@ impl BlossomClient {
57
58
content_type : Option < String > ,
58
59
authorization_options : Option < BlossomAuthorizationOptions > ,
59
60
signer : Option < & T > ,
60
- ) -> Result < BlobDescriptor , Box < dyn Error > >
61
+ ) -> Result < BlobDescriptor , Error >
61
62
where
62
63
T : NostrSigner ,
63
64
{
@@ -70,8 +71,9 @@ impl BlossomClient {
70
71
let mut headers = HeaderMap :: new ( ) ;
71
72
72
73
if let Some ( ct) = content_type {
73
- headers. insert ( CONTENT_TYPE , ct . parse ( ) ?) ;
74
+ headers. insert ( CONTENT_TYPE , HeaderValue :: from_str ( & ct ) ?) ;
74
75
}
76
+
75
77
if let Some ( signer) = signer {
76
78
let default_auth = self . default_auth (
77
79
BlossomAuthorizationVerb :: Upload ,
@@ -87,14 +89,14 @@ impl BlossomClient {
87
89
88
90
request = request. headers ( headers) ;
89
91
90
- let response = request. send ( ) . await ?;
92
+ let response: Response = request. send ( ) . await ?;
91
93
92
94
match response. status ( ) {
93
95
StatusCode :: OK => {
94
96
let descriptor: BlobDescriptor = response. json ( ) . await ?;
95
97
Ok ( descriptor)
96
98
}
97
- _ => Err ( Self :: extract_error ( "Failed to upload blob" , & response) ) ,
99
+ _ => Err ( Error :: response ( "Failed to upload blob" , response) ) ,
98
100
}
99
101
}
100
102
@@ -108,7 +110,7 @@ impl BlossomClient {
108
110
until : Option < Timestamp > ,
109
111
authorization_options : Option < BlossomAuthorizationOptions > ,
110
112
signer : Option < & T > ,
111
- ) -> Result < Vec < BlobDescriptor > , Box < dyn Error > >
113
+ ) -> Result < Vec < BlobDescriptor > , Error >
112
114
where
113
115
T : NostrSigner ,
114
116
{
@@ -146,14 +148,14 @@ impl BlossomClient {
146
148
147
149
request = request. headers ( headers) ;
148
150
149
- let response = request. send ( ) . await ?;
151
+ let response: Response = request. send ( ) . await ?;
150
152
151
153
match response. status ( ) {
152
154
StatusCode :: OK => {
153
155
let descriptors: Vec < BlobDescriptor > = response. json ( ) . await ?;
154
156
Ok ( descriptors)
155
157
}
156
- _ => Err ( Self :: extract_error ( "Failed to list blobs" , & response) ) ,
158
+ _ => Err ( Error :: response ( "Failed to list blobs" , response) ) ,
157
159
}
158
160
}
159
161
@@ -166,7 +168,7 @@ impl BlossomClient {
166
168
range : Option < String > ,
167
169
authorization_options : Option < BlossomAuthorizationOptions > ,
168
170
signer : Option < & T > ,
169
- ) -> Result < Vec < u8 > , Box < dyn Error > >
171
+ ) -> Result < Vec < u8 > , Error >
170
172
where
171
173
T : NostrSigner ,
172
174
{
@@ -193,23 +195,23 @@ impl BlossomClient {
193
195
194
196
request = request. headers ( headers) ;
195
197
196
- let response = request. send ( ) . await ?;
198
+ let response: Response = request. send ( ) . await ?;
197
199
198
200
if response. status ( ) . is_redirection ( ) {
199
201
match response. headers ( ) . get ( "Location" ) {
200
202
Some ( location) => {
201
- let location_str = location. to_str ( ) ?;
203
+ let location_str: & str = location. to_str ( ) ?;
202
204
if !location_str. contains ( & sha256. to_string ( ) ) {
203
- return Err ( "Redirect URL does not contain sha256 hash" . into ( ) ) ;
205
+ return Err ( Error :: RedirectUrlDoesNotContainSha256 ) ;
204
206
}
205
207
}
206
- None => return Err ( "Redirect response missing Location header" . into ( ) ) ,
208
+ None => return Err ( Error :: RedirectResponseMissingLocationHeader ) ,
207
209
}
208
210
}
209
211
210
212
match response. status ( ) {
211
213
StatusCode :: OK | StatusCode :: PARTIAL_CONTENT => Ok ( response. bytes ( ) . await ?. to_vec ( ) ) ,
212
- _ => Err ( Self :: extract_error ( "Failed to get blob" , & response) ) ,
214
+ _ => Err ( Error :: response ( "Failed to get blob" , response) ) ,
213
215
}
214
216
}
215
217
@@ -221,7 +223,7 @@ impl BlossomClient {
221
223
sha256 : Sha256Hash ,
222
224
authorization_options : Option < BlossomAuthorizationOptions > ,
223
225
signer : Option < & T > ,
224
- ) -> Result < bool , Box < dyn Error > >
226
+ ) -> Result < bool , Error >
225
227
where
226
228
T : NostrSigner ,
227
229
{
@@ -247,15 +249,12 @@ impl BlossomClient {
247
249
request = request. headers ( headers) ;
248
250
}
249
251
250
- let response = request. send ( ) . await ?;
252
+ let response: Response = request. send ( ) . await ?;
251
253
252
254
match response. status ( ) {
253
- reqwest:: StatusCode :: OK => Ok ( true ) ,
254
- reqwest:: StatusCode :: NOT_FOUND => Ok ( false ) ,
255
- _ => Err ( Self :: extract_error (
256
- "Unexpected HTTP status code" ,
257
- & response,
258
- ) ) ,
255
+ StatusCode :: OK => Ok ( true ) ,
256
+ StatusCode :: NOT_FOUND => Ok ( false ) ,
257
+ _ => Err ( Error :: response ( "Unexpected HTTP status code" , response) ) ,
259
258
}
260
259
}
261
260
@@ -267,7 +266,7 @@ impl BlossomClient {
267
266
sha256 : Sha256Hash ,
268
267
authorization_options : Option < BlossomAuthorizationOptions > ,
269
268
signer : & T ,
270
- ) -> Result < ( ) , Box < dyn Error > >
269
+ ) -> Result < ( ) , Error >
271
270
where
272
271
T : NostrSigner ,
273
272
{
@@ -287,12 +286,12 @@ impl BlossomClient {
287
286
let auth_header = Self :: build_auth_header ( signer, & final_auth) . await ?;
288
287
headers. insert ( AUTHORIZATION , auth_header) ;
289
288
290
- let response = self . client . delete ( & url) . headers ( headers) . send ( ) . await ?;
289
+ let response: Response = self . client . delete ( & url) . headers ( headers) . send ( ) . await ?;
291
290
292
291
if response. status ( ) . is_success ( ) {
293
292
Ok ( ( ) )
294
293
} else {
295
- Err ( Self :: extract_error ( "Failed to delete blob" , & response) )
294
+ Err ( Error :: response ( "Failed to delete blob" , response) )
296
295
}
297
296
}
298
297
@@ -334,29 +333,17 @@ impl BlossomClient {
334
333
async fn build_auth_header < T > (
335
334
signer : & T ,
336
335
authz : & BlossomAuthorization ,
337
- ) -> Result < HeaderValue , Box < dyn Error > >
336
+ ) -> Result < HeaderValue , Error >
338
337
where
339
338
T : NostrSigner ,
340
339
{
341
- let pubkey = signer. get_public_key ( ) . await ?;
342
- let auth_event = EventBuilder :: blossom_auth ( authz. clone ( ) )
343
- . build ( pubkey)
340
+ let auth_event: Event = EventBuilder :: blossom_auth ( authz. clone ( ) )
344
341
. sign ( signer)
345
342
. await ?;
346
- let auth_bytes = serde_json:: to_vec ( & auth_event) ?;
347
- let encoded_auth = base64:: engine:: general_purpose:: STANDARD . encode ( auth_bytes) ;
348
- HeaderValue :: from_str ( & format ! ( "Nostr {}" , encoded_auth) ) . map_err ( From :: from)
349
- }
350
-
351
- /// Helper function to extract error message from a response.
352
- fn extract_error ( prefix : & str , response : & reqwest:: Response ) -> Box < dyn Error > {
353
- let reason = response
354
- . headers ( )
355
- . get ( "X-Reason" )
356
- . map ( |h| h. to_str ( ) . unwrap_or ( "Unknown reason" ) . to_string ( ) )
357
- . unwrap_or_else ( || "No reason provided" . to_string ( ) ) ;
358
- let message = format ! ( "{}: {} - {}" , prefix, response. status( ) , reason) ;
359
- message. into ( )
343
+ // TODO: use directly event.as_json
344
+ let auth_bytes = serde_json:: to_vec ( & auth_event) . unwrap ( ) ;
345
+ let encoded_auth = general_purpose:: STANDARD . encode ( auth_bytes) ;
346
+ Ok ( HeaderValue :: from_str ( & format ! ( "Nostr {}" , encoded_auth) ) ?)
360
347
}
361
348
}
362
349
@@ -366,7 +353,7 @@ pub struct BlossomAuthorizationOptions {
366
353
/// A human readable string explaining to the user what the events intended use is
367
354
pub content : Option < String > ,
368
355
/// A UNIX timestamp (in seconds) indicating when the authorization should be expired
369
- pub expiration : Option < nostr :: Timestamp > ,
356
+ pub expiration : Option < Timestamp > ,
370
357
/// The type of action authorized by the user
371
358
pub action : Option < BlossomAuthorizationVerb > ,
372
359
/// The scope of the authorization
0 commit comments