1
- use std:: { path:: PathBuf , str:: FromStr } ;
1
+ use std:: {
2
+ fmt:: { Debug , Display } ,
3
+ path:: PathBuf ,
4
+ str:: FromStr ,
5
+ } ;
2
6
3
7
use clap:: { Args , ValueHint , value_parser} ;
4
8
use semver:: Version ;
5
9
use snafu:: { ResultExt , Snafu , ensure} ;
10
+ use strum:: EnumDiscriminants ;
6
11
use url:: Host ;
7
12
8
13
use crate :: build:: {
@@ -39,14 +44,14 @@ pub struct BuildArguments {
39
44
pub target_platform : TargetPlatform ,
40
45
41
46
/// Image registry used in image manifests, URIs, and tags.
47
+ /// The format is host[:port].
42
48
#[ arg(
43
49
short, long,
44
- default_value_t = Self :: default_registry( ) ,
45
- value_parser = Host :: parse,
50
+ default_value_t = HostPort :: localhost( ) ,
46
51
value_hint = ValueHint :: Hostname ,
47
52
help_heading = "Registry Options"
48
53
) ]
49
- pub registry : Host ,
54
+ pub registry : HostPort ,
50
55
51
56
/// The namespace within the given registry.
52
57
#[ arg(
@@ -125,10 +130,6 @@ impl BuildArguments {
125
130
TargetPlatform :: Linux ( Architecture :: Amd64 )
126
131
}
127
132
128
- fn default_registry ( ) -> Host {
129
- Host :: Domain ( String :: from ( "oci.stackable.tech" ) )
130
- }
131
-
132
133
fn default_target_containerfile ( ) -> PathBuf {
133
134
PathBuf :: from ( "Dockerfile" )
134
135
}
@@ -149,3 +150,129 @@ pub fn parse_image_version(input: &str) -> Result<Version, ParseImageVersionErro
149
150
150
151
Ok ( version)
151
152
}
153
+
154
+ #[ derive( Debug , PartialEq , Snafu , EnumDiscriminants ) ]
155
+ pub enum ParseHostPortError {
156
+ #[ snafu( display( "unexpected empty input" ) ) ]
157
+ EmptyInput ,
158
+
159
+ #[ snafu( display( "invalid format, expected host[:port]" ) ) ]
160
+ InvalidFormat ,
161
+
162
+ #[ snafu( display( "failed to parse host" ) ) ]
163
+ InvalidHost { source : url:: ParseError } ,
164
+
165
+ #[ snafu( display( "failed to parse port" ) ) ]
166
+ InvalidPort { source : std:: num:: ParseIntError } ,
167
+ }
168
+
169
+ #[ derive( Clone , Debug ) ]
170
+ pub struct HostPort {
171
+ pub host : Host ,
172
+ pub port : Option < u16 > ,
173
+ }
174
+
175
+ impl Display for HostPort {
176
+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
177
+ match self . port {
178
+ Some ( port) => write ! ( f, "{host}:{port}" , host = self . host) ,
179
+ None => Display :: fmt ( & self . host , f) ,
180
+ }
181
+ }
182
+ }
183
+
184
+ impl FromStr for HostPort {
185
+ type Err = ParseHostPortError ;
186
+
187
+ fn from_str ( input : & str ) -> Result < Self , Self :: Err > {
188
+ ensure ! ( !input. is_empty( ) , EmptyInputSnafu ) ;
189
+
190
+ let parts: Vec < _ > = input. split ( ':' ) . collect ( ) ;
191
+
192
+ match parts[ ..] {
193
+ [ host] => {
194
+ let host = Host :: parse ( host) . context ( InvalidHostSnafu ) ?;
195
+ Ok ( Self { host, port : None } )
196
+ }
197
+ [ host, port] => {
198
+ let host = Host :: parse ( host) . context ( InvalidHostSnafu ) ?;
199
+ let port = u16:: from_str ( port) . context ( InvalidPortSnafu ) ?;
200
+
201
+ Ok ( Self {
202
+ host,
203
+ port : Some ( port) ,
204
+ } )
205
+ }
206
+ _ => InvalidFormatSnafu . fail ( ) ,
207
+ }
208
+ }
209
+ }
210
+
211
+ impl HostPort {
212
+ pub fn localhost ( ) -> Self {
213
+ HostPort {
214
+ host : Host :: Domain ( String :: from ( "localhost" ) ) ,
215
+ port : None ,
216
+ }
217
+ }
218
+ }
219
+
220
+ #[ cfg( test) ]
221
+ mod tests {
222
+ use rstest:: rstest;
223
+ use strum:: IntoDiscriminant ;
224
+ use url:: ParseError ;
225
+
226
+ use super :: * ;
227
+
228
+ enum Either < L , R > {
229
+ Left ( L ) ,
230
+ Right ( R ) ,
231
+ }
232
+
233
+ impl < L , R > Either < L , R >
234
+ where
235
+ L : PartialEq ,
236
+ R : PartialEq ,
237
+ {
238
+ fn is_either ( & self , left : & L , right : & R ) -> bool {
239
+ match self {
240
+ Either :: Left ( l) => l. eq ( left) ,
241
+ Either :: Right ( r) => r. eq ( right) ,
242
+ }
243
+ }
244
+ }
245
+
246
+ #[ rstest]
247
+ #[ case( "registry.example.org:65535" ) ]
248
+ #[ case( "registry.example.org:8080" ) ]
249
+ #[ case( "registry.example.org" ) ]
250
+ #[ case( "example.org:8080" ) ]
251
+ #[ case( "localhost:8080" ) ]
252
+ #[ case( "example.org" ) ]
253
+ #[ case( "localhost" ) ]
254
+ fn valid_host_port ( #[ case] input : & str ) {
255
+ let host_port = HostPort :: from_str ( input) . expect ( "must parse" ) ;
256
+ assert_eq ! ( host_port. to_string( ) , input) ;
257
+ }
258
+
259
+ #[ rustfmt:: skip]
260
+ #[ rstest]
261
+ // We use the discriminants here, because ParseIntErrors cannot be constructed outside of std.
262
+ // As such, it is impossible to fully qualify the error we expect in cases where port parsing
263
+ // fails.
264
+ #[ case( "localhost:65536" , Either :: Right ( ParseHostPortErrorDiscriminants :: InvalidPort ) ) ]
265
+ #[ case( "localhost:" , Either :: Right ( ParseHostPortErrorDiscriminants :: InvalidPort ) ) ]
266
+ // Other errors can be fully qualified.
267
+ #[ case( "with space:" , Either :: Left ( ParseHostPortError :: InvalidHost { source: ParseError :: IdnaError } ) ) ]
268
+ #[ case( "with space" , Either :: Left ( ParseHostPortError :: InvalidHost { source: ParseError :: IdnaError } ) ) ]
269
+ #[ case( ":" , Either :: Left ( ParseHostPortError :: InvalidHost { source: ParseError :: EmptyHost } ) ) ]
270
+ #[ case( "" , Either :: Left ( ParseHostPortError :: EmptyInput ) ) ]
271
+ fn invalid_host_port (
272
+ #[ case] input : & str ,
273
+ #[ case] expected_error : Either < ParseHostPortError , ParseHostPortErrorDiscriminants > ,
274
+ ) {
275
+ let error = HostPort :: from_str ( input) . expect_err ( "must not parse" ) ;
276
+ assert ! ( expected_error. is_either( & error, & error. discriminant( ) ) ) ;
277
+ }
278
+ }
0 commit comments