Use enums for acceptable cli arguments

I'm about 3/4 through the book and have enjoyed it, but I'm starting building some stuff on my own to use the language and get going. I have confused myself a little though :blush:. I'm trying to build on the cli example in the book, by building a cli that does some image transformations. I'm accepting 2 params initially from the cli, the filename and the action.

I only have 4 actions available currently, 1) Gray scale the image, 2) Thumbnail the image, 3) Rotate it, 4) Crop it.

I put these as part of an enum, and then wanted to take the action parameter from the CLI and confirm that the action given i.e "gray" was a valid action. I've included the code, my action_type. I've confused myself as to how to do this, so wondered if someone could point me in the right direction. Or point out a better way to achieve this?

use std::error::Error;

// a lis tof available commands as an enum
pub enum ActionKind {
    Gray(String),
    Thumb(String),
    Rotate(String),
    Crop(String)
}

pub struct Config {
    pub image_path: String,
    pub action: ActionKind
}

impl Config {
    pub fn new(args: &[String]) -> Result<Config, &'static str> {
        if args.len() < 3 {
            return Err("Not enough arguments");
        }

        let image_path = args[1].clone();
        let env_action = args[2].clone();

        fn action_type(action: String) -> Option<ActionKind> {
            match action {
                a => ActionKind::Gray("gray"),
                a => ActionKind::Thumb("thumb"),
                a => ActionKind::Rotate("rotate"),
                a => ActionKind::Crop("crop"),
                _ => None,
            }
        }

        let action = action_type(env_action);
        
        Ok( Config{ image_path, action } )
    }
}

Rust Playground is close to what you wrote.

Thanks so much for helping me :slight_smile:
I previously had my match arms like the way you have them, but I had the wrong return type. I had never thought about wrapping the whole match inside an Ok().

I just had a couple of questions, where you use:

return Err("Not enough arguments".into());

Is that because you want to convert the Err() into a String, in order to match the return signature for new()?

Also,

e @ _ => return Err(format!("Unknown action: {}", e)),

What is the meaning of the @ symbol?

Finally, thanks again :smile: -> Is my approach to this task be better at all?

1 Like

You don’t have to do it inside the Ok - you can assign the result of the match statement into a local and then return Ok(your_local). Doing it inside the Ok is just shorthand for that.

You mean the into() part presumably? Yeah, that’s to turn a string literal (which is always of type &'static str) into an owned String.

It’s a binding - it allows you to capture the value of the match pattern into a variable (binding) so that you can use it inside the match arm. You typically use it when you’re wildcarding part of the pattern (ie using underscore) or if you’re marching on something like a range.

The approach is fine. You may want to define a custom error struct if you think callers will want to match on it to do something specific. Otherwise, just returning a String is fine too.

There’s also a nice CLI crate, clap, that you can use for a more "industrial" CLI interface.

2 Likes

Thanks man! Really appreciate the help and time :smile: