@@ -15,10 +15,37 @@ pub mod creds;
1515
1616pub const DEFAULT_API_URL : & str = "https://api.harmont.dev" ;
1717
18- /// Default execution backend for `hm run` when no `--backend`/`--cloud` flag
19- /// is given.
20- fn default_backend ( ) -> String {
21- "docker" . to_owned ( )
18+ /// Execution backend for `hm run`. Closed set parsed at the config boundary so
19+ /// invalid values are rejected at deserialize time instead of mis-dispatching
20+ /// later, and every consumer match is exhaustively checked by the compiler.
21+ #[ derive( Debug , Copy , Clone , PartialEq , Eq , Serialize , Deserialize ) ]
22+ #[ serde( rename_all = "lowercase" ) ]
23+ pub enum Backend {
24+ Docker ,
25+ Cloud ,
26+ }
27+
28+ impl Default for Backend {
29+ fn default ( ) -> Self {
30+ Backend :: Docker
31+ }
32+ }
33+
34+ impl Backend {
35+ /// Stable lowercase wire/CLI name (matches the `serde` representation).
36+ #[ must_use]
37+ pub fn as_str ( self ) -> & ' static str {
38+ match self {
39+ Backend :: Docker => "docker" ,
40+ Backend :: Cloud => "cloud" ,
41+ }
42+ }
43+ }
44+
45+ impl std:: fmt:: Display for Backend {
46+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
47+ f. write_str ( self . as_str ( ) )
48+ }
2249}
2350
2451#[ derive( Debug , Clone , PartialEq , Eq , Serialize , Deserialize ) ]
@@ -53,8 +80,8 @@ impl Default for Preferences {
5380
5481#[ derive( Debug , Clone , PartialEq , Eq , Serialize , Deserialize ) ]
5582pub struct Config {
56- #[ serde( default = "default_backend" ) ]
57- pub backend : String ,
83+ #[ serde( default ) ]
84+ pub backend : Backend ,
5885 #[ serde( default ) ]
5986 pub cloud : CloudConfig ,
6087 #[ serde( default ) ]
@@ -64,7 +91,7 @@ pub struct Config {
6491impl Default for Config {
6592 fn default ( ) -> Self {
6693 Self {
67- backend : default_backend ( ) ,
94+ backend : Backend :: default ( ) ,
6895 cloud : CloudConfig :: default ( ) ,
6996 preferences : Preferences :: default ( ) ,
7097 }
@@ -156,7 +183,7 @@ mod tests {
156183 #[ test]
157184 fn default_config_values ( ) {
158185 let cfg = Config :: default ( ) ;
159- assert_eq ! ( cfg. backend, "docker" ) ;
186+ assert_eq ! ( cfg. backend, Backend :: Docker ) ;
160187 assert_eq ! ( cfg. cloud. api_url, DEFAULT_API_URL ) ;
161188 assert ! ( cfg. cloud. org. is_none( ) ) ;
162189 assert_eq ! ( cfg. preferences. format, "human" ) ;
@@ -241,7 +268,7 @@ org = "project-org"
241268 #[ test]
242269 fn backend_defaults_docker_and_parses_and_layers ( ) {
243270 // default
244- assert_eq ! ( Config :: default ( ) . backend, "docker" ) ;
271+ assert_eq ! ( Config :: default ( ) . backend, Backend :: Docker ) ;
245272
246273 // user file sets cloud; project file sets docker -> project wins.
247274 let mut user_file = tempfile:: NamedTempFile :: new ( ) . unwrap ( ) ;
@@ -252,19 +279,19 @@ org = "project-org"
252279
253280 let cfg =
254281 Config :: load_from_paths ( Some ( user_file. path ( ) ) , Some ( project_file. path ( ) ) ) . unwrap ( ) ;
255- assert_eq ! ( cfg. backend, "docker" ) ;
282+ assert_eq ! ( cfg. backend, Backend :: Docker ) ;
256283
257284 // user file alone parses "cloud".
258285 let cfg_user = Config :: load_from_paths ( Some ( user_file. path ( ) ) , None ) . unwrap ( ) ;
259- assert_eq ! ( cfg_user. backend, "cloud" ) ;
286+ assert_eq ! ( cfg_user. backend, Backend :: Cloud ) ;
260287 }
261288
262289 #[ tokio:: test( flavor = "multi_thread" , worker_threads = 1 ) ]
263290 async fn save_and_reload_roundtrip ( ) {
264291 let tmp = tempfile:: tempdir ( ) . unwrap ( ) ;
265292 let path = tmp. path ( ) . join ( "config.toml" ) ;
266293 let cfg = Config {
267- backend : default_backend ( ) ,
294+ backend : Backend :: default ( ) ,
268295 cloud : CloudConfig {
269296 org : Some ( "saved-org" . into ( ) ) ,
270297 api_url : DEFAULT_API_URL . to_owned ( ) ,
0 commit comments