diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 69a9da04..b27b3597 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -28,7 +28,7 @@ time = "0.1" memchr = "2" base64 = "0.9" smallvec = "0.6" -pear = "0.0" +pear = { git = "http://github.com/SergioBenitez/pear" } pear_codegen = "0.0" rustls = { version = "0.12.0", optional = true } hyper = { version = "0.10.13", default-features = false } diff --git a/lib/src/config/mod.rs b/lib/src/config/mod.rs index 313a773a..6f80ec17 100644 --- a/lib/src/config/mod.rs +++ b/lib/src/config/mod.rs @@ -378,7 +378,7 @@ impl RocketConfig { let key = key[ENV_VAR_PREFIX.len()..].to_lowercase(); let toml_val = match parse_simple_toml_value(&val) { Ok(val) => val, - Err(e) => return Err(ConfigError::BadEnvVal(key, val, e.into())) + Err(e) => return Err(ConfigError::BadEnvVal(key, val, e)) }; for env in &Environment::all() { diff --git a/lib/src/config/toml_ext.rs b/lib/src/config/toml_ext.rs index 103ce49d..42dcb405 100644 --- a/lib/src/config/toml_ext.rs +++ b/lib/src/config/toml_ext.rs @@ -3,64 +3,85 @@ use std::collections::BTreeMap; use config::Value; -pub fn parse_simple_toml_value(string: &str) -> Result { - let string = string.trim(); - if string.is_empty() { - return Err("value is empty") +use pear::{ParseResult, ParseError}; +use pear::parsers::*; +use pear::combinators::*; + +#[inline(always)] +pub fn is_whitespace(byte: char) -> bool { + byte == ' ' || byte == '\t' +} + +#[inline(always)] +pub fn is_number_token(byte: char) -> bool { + match byte { + '0'...'9' | '.' | '-' => true, + _ => false } +} - let value = if let Ok(int) = string.parse::() { +// FIXME: Silence warning for `digits.parse`. +#[parser] +fn number<'a>(input: &mut &'a str) -> ParseResult<&'a str, Value> { + let digits = take_some_while(is_number_token); + if let Ok(int) = digits.parse::() { Value::Integer(int) - } else if let Ok(float) = string.parse::() { - Value::Float(float) - } else if let Ok(boolean) = string.parse::() { - Value::Boolean(boolean) - } else if string.starts_with('{') { - if !string.ends_with('}') { - return Err("value is missing closing '}'") - } - - let mut table = BTreeMap::new(); - let inner = &string[1..string.len() - 1].trim(); - if !inner.is_empty() { - for key_val in inner.split(',') { - let (key, val) = match key_val.find('=') { - Some(i) => (&key_val[..i], &key_val[(i + 1)..]), - None => return Err("missing '=' in dicitonary key/value pair") - }; - - let key = key.trim().to_string(); - let val = parse_simple_toml_value(val.trim())?; - table.insert(key, val); - } - } - - Value::Table(table) - } else if string.starts_with('[') { - if !string.ends_with(']') { - return Err("value is missing closing ']'") - } - - let mut vals = vec![]; - let inner = &string[1..string.len() - 1].trim(); - if !inner.is_empty() { - for val_str in inner.split(',') { - vals.push(parse_simple_toml_value(val_str.trim())?); - } - } - - Value::Array(vals) - } else if string.starts_with('"') { - if !string[1..].ends_with('"') { - return Err("value is missing closing '\"'"); - } - - Value::String(string[1..string.len() - 1].to_string()) } else { - Value::String(string.to_string()) + let v = from!(digits.parse::()); + Value::Float(v) + } +} + +#[parser] +fn array<'a>(input: &mut &'a str) -> ParseResult<&'a str, Value> { + let array = (eat('['), collect!(value(), eat(',')), eat(']')).1; + Value::Array(array) +} + +// FIXME: Be more permissive here? +#[parser] +fn key<'a>(input: &mut &'a str) -> ParseResult<&'a str, String> { + take_some_while(|c| match c { + '0'...'9' | 'A'...'Z' | 'a'...'z' | '_' | '-' => true, + _ => false + }).to_string() +} + +#[parser] +fn table<'a>(input: &mut &'a str) -> ParseResult<&'a str, Value> { + eat('{'); + + let mut values = BTreeMap::new(); + try_repeat_while!(eat(','), { + let key = surrounded(key, is_whitespace); + (eat('='), skip_while(is_whitespace)); + values.insert(key, value()) + }); + + eat('}'); + Value::Table(values) +} + +#[parser] +fn value<'a>(input: &mut &'a str) -> ParseResult<&'a str, Value> { + skip_while(is_whitespace); + let val = switch! { + eat_slice("true") => Value::Boolean(true), + eat_slice("false") => Value::Boolean(false), + peek('{') => table(), + peek('[') => array(), + peek_if(is_number_token) => number(), + peek('"') => Value::String(delimited('"', |_| true, '"').to_string()), + _ => Value::String(take_some_while(|c| c != ',' && c != '}' && c != ']').to_string()) }; - Ok(value) + skip_while(is_whitespace); + val +} + +pub fn parse_simple_toml_value(mut input: &str) -> Result { + let result: Result> = parse!(&mut input, (value(), eof()).0).into(); + result.map_err(|e| e.to_string()) } /// A simple wrapper over a `Value` reference with a custom implementation of @@ -92,7 +113,7 @@ impl<'a> fmt::Display for LoggedValue<'a> { mod test { use std::collections::BTreeMap; use super::parse_simple_toml_value; - use super::Value::*; + use super::Value::{self, *}; macro_rules! assert_parse { ($string:expr, $value:expr) => ( @@ -109,7 +130,7 @@ mod test { assert_parse!("1.32", Float(1.32)); assert_parse!("true", Boolean(true)); assert_parse!("false", Boolean(false)); - assert_parse!("hello, WORLD!", String("hello, WORLD!".into())); + assert_parse!("\"hello, WORLD!\"", String("hello, WORLD!".into())); assert_parse!("hi", String("hi".into())); assert_parse!("\"hi\"", String("hi".into())); @@ -119,11 +140,13 @@ mod test { assert_parse!("[1.32, 2]", Array(vec![1.32.into(), 2.into()])); assert_parse!("{}", Table(BTreeMap::new())); + assert_parse!("{a=b}", Table({ let mut map = BTreeMap::new(); map.insert("a".into(), "b".into()); map })); + assert_parse!("{v=1, on=true,pi=3.14}", Table({ let mut map = BTreeMap::new(); map.insert("v".into(), 1.into()); @@ -131,5 +154,23 @@ mod test { map.insert("pi".into(), 3.14.into()); map })); + + assert_parse!("{v=[1, 2, 3], v2=[a, \"b\"], on=true,pi=3.14}", Table({ + let mut map = BTreeMap::new(); + map.insert("v".into(), vec![1, 2, 3].into()); + map.insert("v2".into(), vec!["a", "b"].into()); + map.insert("on".into(), true.into()); + map.insert("pi".into(), 3.14.into()); + map + })); + + assert_parse!("{v=[[1], [2, 3], [4,5]]}", Table({ + let mut map = BTreeMap::new(); + let first: Value = vec![1].into(); + let second: Value = vec![2, 3].into(); + let third: Value = vec![4, 5].into(); + map.insert("v".into(), vec![first, second, third].into()); + map + })); } }