convert 0.2.0

A command line tool written in rust which converts between currencies.
extern crate clap;
extern crate curl;
extern crate env_logger;
extern crate serde_json;
extern crate failure;
#[macro_use]
extern crate log;
extern crate json;
#[macro_use]

use clap::{App, Arg};
use failure::Error;
use curl::http;
use json::parse;
use std::f32;
use serde_json::Value;

#[derive(Debug)]
struct Conversion {
    amount: f32,
    rate: f32,
}

impl Conversion {
    fn new(amount: f32, rate: f32) -> Self {
        Conversion {
            amount: amount,
            rate: rate,
        }
    }
}


fn main() {
    use std::process::exit;

    if let Err(err) = run() {
        debug!("{:?}", err);
        eprintln!("{}", err);
        for cause in err.causes().skip(1) {
            eprintln!("Caused by: {}", cause);
        }
        exit(1);
    }
}

fn run() -> Result<(), Error> {
    env_logger::init()?; 
    let matches = App::new("convert")
        .version("0.2.0")
        .author("James McDermott <james.mcdermott89@gmail.com>")
        .about("A command line tool written in rust which converts between currencies.")
        .arg(Arg::with_name("list")
                 .short("l")
                 .long("list")
                 .value_name("list")
                 .takes_value(false)
                 .help("displays all supported currencies."))
        .arg(Arg::with_name("amount")
                 .required(false)
                 .takes_value(true)
                 .index(1)
                 .help("amount to convert from base currency to desired currency."))
        .arg(Arg::with_name("base")
                 .required(false)
                 .takes_value(true)
                 .index(2)
                 .help("base currency, i.e EUR, USD, etc."))
        .arg(Arg::with_name("desired")
                 .required(false)
                 .takes_value(true)
                 .index(3)
                 .help("desired currency, i.e, EUR, USD, etc"))
        .get_matches();
    if matches.is_present("list") {
        println!("convert supports the following currencies: ");
        let url = "https://api.fixer.io/latest?base=ZAR".to_string(); 
        let response = http::handle()
            .get(url)
            .exec()
            .unwrap();
        let body = std::str::from_utf8(response.get_body()).unwrap();
        let json: Value = serde_json::from_str(body).unwrap_or_else(|e| {
            panic!("Failed to parse json; error is {}", e);
        });
        let obj = json.as_object().and_then(|object| object.get("rates")).and_then(|links| links.as_object()).unwrap_or_else(|| {
            panic!("Failed to get '_links' value from json");
        });
        let mut count = 0;
        for (curr, val) in obj.iter() {
            if count == 4 {
                println!("");
                count = 0;
            }
            print!("    - {}", curr);
            count += 1;
        }
        println!("    - ZAR\n");
    }
    else {
        let amount = matches.value_of("amount").unwrap().to_string().parse::<f32>()?;
        let base = matches.value_of("base").unwrap();
        let desired = matches.value_of("desired").unwrap();
        let base_link = "https://api.fixer.io/latest?base=".to_string();
        let curr = &base;
        let url = base_link + &curr; 
        let response = http::handle()
            .get(url)
            .exec()
            .unwrap();
        let body = std::str::from_utf8(response.get_body()).unwrap();
        let data = json::parse(body).unwrap_or_else(|e| {
            panic!("Failed to parse json; error is {}", e);
        });
        let rate = data["rates"][desired].to_string().parse::<f32>()?;
        let conversion = Conversion::new(amount, rate);
        println!("{} {} is worth {:.2} {}", conversion.amount, base, conversion.amount * conversion.rate, desired);
    }
    Ok(())
}