Config Solutions

Embossed compass legend

So, because match gave me some problems (hrm, what Rust troubles are yet to come!?), this took me a minute to get right. These changes allow us to configure the encoding characters to be defined in .json config files. Plus, a platform level (development, stage, production) can be set and configured values can be overridden via environment variables.

Code Dump Coming…

extern crate config;
extern crate serde;

#[macro_use]
extern crate serde_derive;

mod settings;

use settings::Settings;

fn main() {
    for arg in std::env::args().skip(1) {
      let encoded = encode(&arg);
      let decoded = decode(&encoded);
      assert_eq!(arg, decoded);

      println!("'{}' encodes as '{}' and decode works",
          &arg, &encoded);
    }
 }

fn encode(orig: &str) -> String {
    let settings = Settings::new();

    let mut advance = '1';
    let mut print = '0';
    match settings {
        Ok(s) => {
            advance = s.advance;
            print = s.print;
        },
        Err(_) => {}
    }

    let mut str = String::new();
    let mut cur: u8 = 0;

    for c in orig.chars() {
      while c != cur as char {
        cur = cur.wrapping_add(1);
        str.push(advance);
      }
      str.push(print);
    }
    str
}

fn decode(code: &str) -> String {
    let settings = Settings::new();

    let mut advance = '1';
    let mut print = '0';
    match settings {
        Ok(s) => {
            advance = s.advance;
            print = s.print;
        },
        Err(_) => {}
    }
    let mut str = String::new();
    let mut cur: u8 = 0;

    for c in code.chars() {
        match c {
            a if a == advance => cur = cur.wrapping_add(1),
            b if b == print => str.push(cur as char),
            _ => {},
        }
    }
    str
}



And now I have settings.rs as well:

use std::env;
use config::{ConfigError, Config, File, Environment};

#[derive(Debug, Deserialize)]
pub struct Settings {
    pub debug: bool,
    pub advance: char,
    pub print: char,
}

impl Settings {
    pub fn new() -> Result<Self, ConfigError> {
        let mut s = Config::new();

        // Start off by merging in the "default" configuration file
        s.merge(File::with_name("conf/default"))?;

        // Add in the current environment file
        // Default to 'development' env
        // Note that this file is _optional_
        let env = env::var("RUN_MODE").unwrap_or("development".into());
        s.merge(File::with_name(&format!("conf/{}", env)).required(false))?;

        // Add in a local configuration file
        // This file shouldn't be checked in to git
        s.merge(File::with_name("conf/local").required(false))?;

        // Add in settings from the environment (with a prefix of APP)
        // Eg.. `APP_DEBUG=1 ./target/app` would set the `debug` key
        s.merge(Environment::with_prefix("app"))?;

        // You may also programmatically change settings
        //s.set("database.url", "postgres://")?;

        // Now that we're done, let's access our configuration
        //println!("debug: {:?}", s.get_bool("debug"));
        //println!("database: {:?}", s.get::<String>("database.url"));

        // You can deserialize (and thus freeze) the entire configuration as
        s.try_into()
    }
}

and some config files too!

conf/default.json:

{
    "debug": 1
}

conf/development.json:

 {
    "advance": "#",
    "print": "@"
}

Can I Talk My Way Through That?

Feel free to comment about what I did wrong, because at the very least, my settings processing seems wrong and repetitive! As bad as globals are, is that possible in Rust and better than passing it around or certainly better than having to go assign settings in every method that needs it?

Also, I understand making the settings struct be pub but why did I have to make each of its properties pub as well – that doesn’t seem right.

It took me a while to fully realize why match was called match and not switch! Because it is based on patterns and not straight conditionals! But these aren’t perl regex patterns, so don’t get excited! Even though they aren’t actual regex’s, they ARE “patterns”, so matching one variable against another isn’t straightforward like with switch/case.

    match c {
        a if a == advance => cur = cur.wrapping_add(1),
        b if b == print => str.push(cur as char),
        _ => {},
    }

So, to check if character c matches variables advance or print, we need (I think) to do it this way: a and b in each of those match arms serve as temporary variables to make the comparison and are lost as soon as the match statement ends. I’ll come back when I figure out the correct way, but hopefully someone will let me know!