1
1
use std:: io:: { self , Write } ;
2
2
use std:: path:: Path ;
3
3
4
- use clap:: builder:: { IntoResettable , Str } ;
5
4
use clap:: { Arg , ArgMatches , Command } ;
5
+ use protocol:: ProtocolError ;
6
6
use semver:: Version ;
7
+ use thiserror:: Error ;
7
8
8
- use common_config_parser:: { parse_file, types:: Config } ;
9
+ use common_config_parser:: { parse_file, types:: Config , ParseError } ;
9
10
use core_run:: { Axon , KeyProvider , SecioKeyPair } ;
10
11
use protocol:: types:: RichBlock ;
11
12
13
+ #[ non_exhaustive]
14
+ #[ derive( Error , Debug ) ]
15
+ pub enum Error {
16
+ // Boxing so the error type isn't too large (clippy::result-large-err).
17
+ #[ error( transparent) ]
18
+ CheckingVersion ( Box < CheckingVersionError > ) ,
19
+ #[ error( "reading data version: {0}" ) ]
20
+ ReadingVersion ( #[ source] io:: Error ) ,
21
+ #[ error( "writing data version: {0}" ) ]
22
+ WritingVersion ( #[ source] io:: Error ) ,
23
+
24
+ #[ error( "parsing config: {0}" ) ]
25
+ ParsingConfig ( #[ source] ParseError ) ,
26
+ #[ error( "getting parent directory of config file" ) ]
27
+ GettingParent ,
28
+ #[ error( "parsing genesis: {0}" ) ]
29
+ ParsingGenesis ( #[ source] ParseError ) ,
30
+
31
+ #[ error( transparent) ]
32
+ Running ( ProtocolError ) ,
33
+ }
34
+
35
+ #[ non_exhaustive]
36
+ #[ derive( Error , Debug ) ]
37
+ #[ cfg_attr( test, derive( PartialEq , Eq ) ) ]
38
+ #[ error( "data version({data}) is not compatible with the current axon version({current}), version >= {least_compatible} is supported" ) ]
39
+ pub struct CheckingVersionError {
40
+ pub current : Version ,
41
+ pub data : Version ,
42
+ pub least_compatible : Version ,
43
+ }
44
+
45
+ pub type Result < T , E = Error > = std:: result:: Result < T , E > ;
46
+
12
47
pub struct AxonCli {
13
48
version : Version ,
14
49
matches : ArgMatches ,
15
50
}
16
51
17
52
impl AxonCli {
18
- pub fn init ( ver : impl IntoResettable < Str > ) -> Self {
53
+ pub fn init ( axon_version : Version , cli_version : & ' static str ) -> Self {
19
54
let matches = Command :: new ( "axon" )
20
- . version ( ver )
55
+ . version ( cli_version )
21
56
. arg (
22
57
Arg :: new ( "config_path" )
23
58
. short ( 'c' )
@@ -37,19 +72,24 @@ impl AxonCli {
37
72
. subcommand ( Command :: new ( "run" ) . about ( "Run axon process" ) ) ;
38
73
39
74
AxonCli {
40
- version : Version :: parse ( matches . get_version ( ) . unwrap ( ) ) . unwrap ( ) ,
75
+ version : axon_version ,
41
76
matches : matches. get_matches ( ) ,
42
77
}
43
78
}
44
79
45
- pub fn start ( & self ) {
80
+ pub fn start ( & self ) -> Result < ( ) > {
46
81
self . start_with_custom_key_provider :: < SecioKeyPair > ( None )
47
82
}
48
83
49
- pub fn start_with_custom_key_provider < K : KeyProvider > ( & self , key_provider : Option < K > ) {
84
+ pub fn start_with_custom_key_provider < K : KeyProvider > (
85
+ & self ,
86
+ key_provider : Option < K > ,
87
+ ) -> Result < ( ) > {
50
88
let config_path = self . matches . get_one :: < String > ( "config_path" ) . unwrap ( ) ;
51
- let path = Path :: new ( & config_path) . parent ( ) . unwrap ( ) ;
52
- let mut config: Config = parse_file ( config_path, false ) . unwrap ( ) ;
89
+ let path = Path :: new ( & config_path)
90
+ . parent ( )
91
+ . ok_or ( Error :: GettingParent ) ?;
92
+ let mut config: Config = parse_file ( config_path, false ) . map_err ( Error :: ParsingConfig ) ?;
53
93
54
94
if let Some ( ref mut f) = config. rocksdb . options_file {
55
95
* f = path. join ( & f)
@@ -58,47 +98,53 @@ impl AxonCli {
58
98
self . matches . get_one :: < String > ( "genesis_path" ) . unwrap ( ) ,
59
99
true ,
60
100
)
61
- . unwrap ( ) ;
101
+ . map_err ( Error :: ParsingGenesis ) ? ;
62
102
63
- self . check_version ( & config) ;
103
+ self . check_version ( & config) ? ;
64
104
65
105
register_log ( & config) ;
66
106
67
- Axon :: new ( config, genesis) . run ( key_provider) . unwrap ( ) ;
107
+ Axon :: new ( config, genesis)
108
+ . run ( key_provider)
109
+ . map_err ( Error :: Running ) ?;
110
+ Ok ( ( ) )
68
111
}
69
112
70
- fn check_version ( & self , config : & Config ) {
71
- if !config. data_path . exists ( ) {
72
- std:: fs:: create_dir_all ( & config. data_path ) . unwrap ( ) ;
73
- }
74
-
113
+ fn check_version ( & self , config : & Config ) -> Result < ( ) > {
114
+ // Won't panic because parent of data_path_for_version() is data_path.
75
115
check_version (
76
116
& config. data_path_for_version ( ) ,
77
117
& self . version ,
78
- & latest_compatible_version ( ) ,
79
- ) ;
118
+ latest_compatible_version ( ) ,
119
+ )
80
120
}
81
121
}
82
122
83
- fn check_version ( p : & Path , current : & Version , least_compatible : & Version ) {
123
+ /// # Panics
124
+ ///
125
+ /// If p.parent() is None.
126
+ fn check_version ( p : & Path , current : & Version , least_compatible : Version ) -> Result < ( ) > {
84
127
let ver_str = match std:: fs:: read_to_string ( p) {
85
128
Ok ( x) => x,
86
129
Err ( e) if e. kind ( ) == io:: ErrorKind :: NotFound => "" . into ( ) ,
87
- Err ( e) => panic ! ( "failed to read version: {e}" ) ,
130
+ Err ( e) => return Err ( Error :: ReadingVersion ( e ) ) ,
88
131
} ;
89
132
90
133
if ver_str. is_empty ( ) {
91
- return atomic_write ( p, current. to_string ( ) . as_bytes ( ) ) . unwrap ( ) ;
134
+ atomic_write ( p, current. to_string ( ) . as_bytes ( ) ) . map_err ( Error :: WritingVersion ) ?;
135
+ return Ok ( ( ) ) ;
92
136
}
93
137
94
138
let prev_version = Version :: parse ( & ver_str) . unwrap ( ) ;
95
- if prev_version < * least_compatible {
96
- panic ! (
97
- "The previous version {} is not compatible with the current version {}" ,
98
- prev_version, current
99
- ) ;
139
+ if prev_version < least_compatible {
140
+ return Err ( Error :: CheckingVersion ( Box :: new ( CheckingVersionError {
141
+ least_compatible,
142
+ data : prev_version,
143
+ current : current. clone ( ) ,
144
+ } ) ) ) ;
100
145
}
101
- atomic_write ( p, current. to_string ( ) . as_bytes ( ) ) . unwrap ( ) ;
146
+ atomic_write ( p, current. to_string ( ) . as_bytes ( ) ) . map_err ( Error :: WritingVersion ) ?;
147
+ Ok ( ( ) )
102
148
}
103
149
104
150
/// Write content to p atomically. Create the parent directory if it doesn't
@@ -107,7 +153,7 @@ fn check_version(p: &Path, current: &Version, least_compatible: &Version) {
107
153
/// # Panics
108
154
///
109
155
/// if p.parent() is None.
110
- fn atomic_write ( p : & Path , content : & [ u8 ] ) -> std :: io:: Result < ( ) > {
156
+ fn atomic_write ( p : & Path , content : & [ u8 ] ) -> io:: Result < ( ) > {
111
157
let parent = p. parent ( ) . unwrap ( ) ;
112
158
113
159
std:: fs:: create_dir_all ( parent) ?;
@@ -147,28 +193,39 @@ mod tests {
147
193
use super :: * ;
148
194
149
195
#[ test]
150
- fn test_check_version ( ) {
196
+ fn test_check_version ( ) -> Result < ( ) > {
151
197
let tmp = NamedTempFile :: new ( ) . unwrap ( ) ;
152
198
let p = tmp. path ( ) ;
153
199
// We just want NamedTempFile to delete the file on drop. We want to
154
200
// start with the file not exist.
155
201
std:: fs:: remove_file ( p) . unwrap ( ) ;
156
202
157
- let least_compatible = "0.1.0-alpha.9" . parse ( ) . unwrap ( ) ;
203
+ let latest_compatible : Version = "0.1.0-alpha.9" . parse ( ) . unwrap ( ) ;
158
204
159
- check_version ( p, & "0.1.15" . parse ( ) . unwrap ( ) , & least_compatible ) ;
205
+ check_version ( p, & "0.1.15" . parse ( ) . unwrap ( ) , latest_compatible . clone ( ) ) ? ;
160
206
assert_eq ! ( std:: fs:: read_to_string( p) . unwrap( ) , "0.1.15" ) ;
161
207
162
- check_version ( p, & "0.2.0" . parse ( ) . unwrap ( ) , & least_compatible ) ;
208
+ check_version ( p, & "0.2.0" . parse ( ) . unwrap ( ) , latest_compatible ) ? ;
163
209
assert_eq ! ( std:: fs:: read_to_string( p) . unwrap( ) , "0.2.0" ) ;
210
+
211
+ Ok ( ( ) )
164
212
}
165
213
166
- #[ should_panic = "The previous version" ]
167
214
#[ test]
168
- fn test_check_version_failure ( ) {
215
+ fn test_check_version_failure ( ) -> Result < ( ) > {
169
216
let tmp = NamedTempFile :: new ( ) . unwrap ( ) ;
170
217
let p = tmp. path ( ) ;
171
- check_version ( p, & "0.1.0" . parse ( ) . unwrap ( ) , & "0.1.0" . parse ( ) . unwrap ( ) ) ;
172
- check_version ( p, & "0.2.0" . parse ( ) . unwrap ( ) , & "0.2.0" . parse ( ) . unwrap ( ) ) ;
218
+ check_version ( p, & "0.1.0" . parse ( ) . unwrap ( ) , "0.1.0" . parse ( ) . unwrap ( ) ) ?;
219
+ let err =
220
+ check_version ( p, & "0.2.2" . parse ( ) . unwrap ( ) , "0.2.0" . parse ( ) . unwrap ( ) ) . unwrap_err ( ) ;
221
+ match err {
222
+ Error :: CheckingVersion ( e) => assert_eq ! ( * e, CheckingVersionError {
223
+ current: "0.2.2" . parse( ) . unwrap( ) ,
224
+ least_compatible: "0.2.0" . parse( ) . unwrap( ) ,
225
+ data: "0.1.0" . parse( ) . unwrap( ) ,
226
+ } ) ,
227
+ e => panic ! ( "unexpected error {e}" ) ,
228
+ }
229
+ Ok ( ( ) )
173
230
}
174
231
}
0 commit comments