@@ -11,7 +11,7 @@ pub enum RequestStrategy {
11
11
/// Run the request once.
12
12
Once ,
13
13
/// Run it once with a given idempotency key.
14
- Idempotent ( String ) ,
14
+ Idempotent ( IdempotencyKey ) ,
15
15
/// This strategy will retry the request up to the
16
16
/// specified number of times using the same, random,
17
17
/// idempotency key, up to n times.
@@ -64,25 +64,83 @@ impl RequestStrategy {
64
64
/// Send the request once with a generated UUID.
65
65
#[ cfg( feature = "uuid" ) ]
66
66
pub fn idempotent_with_uuid ( ) -> Self {
67
- use uuid:: Uuid ;
68
- Self :: Idempotent ( Uuid :: new_v4 ( ) . to_string ( ) )
67
+ Self :: Idempotent ( IdempotencyKey :: new_uuid_v4 ( ) )
69
68
}
70
69
71
70
/// Extract the current idempotency key to use for the next request, if any.
72
- pub fn get_key ( & self ) -> Option < String > {
71
+ pub fn get_key ( & self ) -> Option < IdempotencyKey > {
73
72
match self {
74
73
RequestStrategy :: Once => None ,
75
74
RequestStrategy :: Idempotent ( key) => Some ( key. clone ( ) ) ,
76
75
#[ cfg( feature = "uuid" ) ]
77
76
RequestStrategy :: Retry ( _) | RequestStrategy :: ExponentialBackoff ( _) => {
78
- Some ( uuid :: Uuid :: new_v4 ( ) . to_string ( ) )
77
+ Some ( IdempotencyKey :: new_uuid_v4 ( ) )
79
78
}
80
79
#[ cfg( not( feature = "uuid" ) ) ]
81
80
RequestStrategy :: Retry ( _) | RequestStrategy :: ExponentialBackoff ( _) => None ,
82
81
}
83
82
}
84
83
}
85
84
85
+ #[ derive( Debug , Clone , PartialEq , Eq ) ]
86
+ #[ repr( transparent) ]
87
+ /// Represents valid idempotency key
88
+ /// - Cannot be empty
89
+ /// - Cannot be longer than 255 charachters
90
+ pub struct IdempotencyKey ( String ) ;
91
+
92
+ #[ derive( Debug , thiserror:: Error ) ]
93
+ /// Error that can be returned when constructing [`IdempotencyKey`]
94
+ pub enum IdempotentKeyError {
95
+ #[ error( "Idempotency Key cannot be empty" ) ]
96
+ /// Idempotency key cannot be empty
97
+ EmptyKey ,
98
+ #[ error( "Idempotency key cannot be longer than 255 characters (you supplied: {0})" ) ]
99
+ /// Idempotency key cannot be longer than 255 characters
100
+ KeyTooLong ( usize ) ,
101
+ }
102
+
103
+ impl IdempotencyKey {
104
+ /// Creates new validated idempotency key.
105
+ /// - Cannot be empty
106
+ /// - Cannot be longer than 255 charachters
107
+ pub fn new ( val : impl AsRef < str > ) -> Result < Self , IdempotentKeyError > {
108
+ let val = val. as_ref ( ) ;
109
+ if val. is_empty ( ) {
110
+ Err ( IdempotentKeyError :: EmptyKey )
111
+ } else if val. len ( ) > 255 {
112
+ Err ( IdempotentKeyError :: KeyTooLong ( val. len ( ) ) )
113
+ } else {
114
+ Ok ( Self ( val. to_owned ( ) ) )
115
+ }
116
+ }
117
+
118
+ #[ cfg( feature = "uuid" ) ]
119
+ /// Generates new UUID as new idempotency key
120
+ pub fn new_uuid_v4 ( ) -> Self {
121
+ let uuid = uuid:: Uuid :: new_v4 ( ) . to_string ( ) ;
122
+ Self ( uuid)
123
+ }
124
+
125
+ /// Borrows self as string slice
126
+ pub fn as_str ( & self ) -> & str {
127
+ & self . 0
128
+ }
129
+
130
+ /// Consumes self and returns inner string
131
+ pub fn into_inner ( self ) -> String {
132
+ self . 0
133
+ }
134
+ }
135
+
136
+ impl TryFrom < String > for IdempotencyKey {
137
+ type Error = IdempotentKeyError ;
138
+
139
+ fn try_from ( value : String ) -> Result < Self , Self :: Error > {
140
+ Self :: new ( value)
141
+ }
142
+ }
143
+
86
144
fn calculate_backoff ( retry_count : u32 ) -> Duration {
87
145
Duration :: from_secs ( 2_u64 . pow ( retry_count) )
88
146
}
@@ -102,11 +160,13 @@ mod tests {
102
160
use std:: time:: Duration ;
103
161
104
162
use super :: { Outcome , RequestStrategy } ;
163
+ use crate :: IdempotencyKey ;
105
164
106
165
#[ test]
107
166
fn test_idempotent_strategy ( ) {
108
- let strategy = RequestStrategy :: Idempotent ( "key" . to_string ( ) ) ;
109
- assert_eq ! ( strategy. get_key( ) , Some ( "key" . to_string( ) ) ) ;
167
+ let key: IdempotencyKey = "key" . to_string ( ) . try_into ( ) . unwrap ( ) ;
168
+ let strategy = RequestStrategy :: Idempotent ( key. clone ( ) ) ;
169
+ assert_eq ! ( strategy. get_key( ) , Some ( key) ) ;
110
170
}
111
171
112
172
#[ test]
@@ -122,7 +182,7 @@ mod tests {
122
182
fn test_uuid_idempotency ( ) {
123
183
use uuid:: Uuid ;
124
184
let strategy = RequestStrategy :: Retry ( 3 ) ;
125
- assert ! ( Uuid :: parse_str( & strategy. get_key( ) . unwrap( ) ) . is_ok( ) ) ;
185
+ assert ! ( Uuid :: parse_str( & strategy. get_key( ) . unwrap( ) . as_str ( ) ) . is_ok( ) ) ;
126
186
}
127
187
128
188
#[ test]
0 commit comments