map.insert("include".to_string(), value);
CV::Table(map, Definition::Cli)
} else {
- // We only want to allow "dotted keys" (see https://toml.io/en/v1.0.0#keys), which
- // are defined as .-separated "simple" keys, where a simple key is either a quoted
- // or unquoted key, as defined in the ANBF:
- //
- // unquoted-key = 1*( ALPHA / DIGIT / %x2D / %x5F ) ; A-Z / a-z / 0-9 / - / _
- // quoted-key = basic-string / literal-string
- //
- // https://github.com/toml-lang/toml/blob/1.0.0/toml.abnf#L50-L51
- //
- // We don't want to bring in a full parser, but luckily since we're just verifying
- // a subset of the format here, the code isn't too hairy. The big thing we need to
- // deal with is quoted strings and escaped characters.
- let mut in_quoted_string = false;
- let mut in_literal_string = false;
- let mut chars = arg.chars();
- let mut depth = 1;
- while let Some(c) = chars.next() {
- match c {
- '\'' if !in_quoted_string => {
- in_literal_string = !in_literal_string;
- }
- _ if in_literal_string => {
- // The spec only allows certain characters here, but it doesn't matter
- // for the purposes of checking if this is indeed a dotted key. If the
- // user gave an invalid expression, it'll be detected when we parse as
- // TOML later.
- //
- // Note that escapes are not permitted in literal strings.
- }
- '"' => {
- in_quoted_string = !in_quoted_string;
- }
- '\\' => {
- // Whatever the next char is, it's not a double quote or an escape.
- // Technically escape can capture more than one char, but that's only
- // possible if uXXXX and UXXXXXXXX Unicode specfiers, which are all hex
- // characters anyway, and therefore won't cause a problem.
- let _ = chars.next();
- }
- _ if in_quoted_string => {
- // Anything goes within quotes as far as we're concerned
- }
- 'A'..='Z' | 'a'..='z' | '0'..='9' | '-' | '_' => {
- // These are fine as part of a dotted key
- }
- '.' => {
- // This is a dotted key separator -- dots are okay
- depth += 1;
- }
- ' ' | '\t' => {
- // This kind of whitespace is acceptable in dotted keys.
- // Technically it's only allowed around the dots and =,
- // but there's no need for us to be picky about that here.
- }
- '=' => {
- // We didn't hit anything questionable before hitting the first =
- // (that is not within a quoted string), so this is a dotted key
- // expression.
+ // We only want to allow "dotted key" (see https://toml.io/en/v1.0.0#keys)
+ // expressions followed by a value that's not an "inline table"
+ // (https://toml.io/en/v1.0.0#inline-table). Easiest way to check for that is to
+ // parse the value as a toml_edit::Document, and check that the (single)
+ // inner-most table is set via dotted keys.
+ let doc: toml_edit::Document = arg.parse().with_context(|| {
+ format!("failed to parse value from --config argument `{}` as a dotted key expression", arg)
+ })?;
+ let ok = {
+ let mut got_to_value = false;
+ let mut table = doc.as_table();
+ let mut is_root = true;
+ while table.is_dotted() || is_root {
+ is_root = false;
+ if table.len() != 1 {
break;
}
- _ => {
- // We hit some character before the = that isn't permitted in a dotted
- // key expression, so the user is trying to pass something more
- // involved.
- bail!(
- "--config argument `{}` was not a TOML dotted key expression (a.b.c = _)",
- arg
- );
- }
- }
- }
- let toml_v: toml::Value = toml::de::from_str(arg)
- .with_context(|| format!("failed to parse --config argument `{}`", arg))?;
-
- // To avoid questions around nested table merging, we disallow tables with more
- // than one value -- such changes should instead take the form of multiple dotted
- // key expressions passed as separate --config arguments.
- {
- let mut table = toml_v.as_table();
- while let Some(t) = table {
- if t.len() != 1 {
- bail!(
- "--config argument `{}` expected exactly one key=value pair, got {} keys",
- arg,
- t.len()
- );
- }
- if depth == 0 {
+ let (_, n) = table.iter().next().expect("len() == 1 above");
+ if let Some(nt) = n.as_table() {
+ table = nt;
+ } else if n.is_inline_table() {
bail!(
- "--config argument `{}` uses inline table values, which are not accepted",
+ "--config argument `{}` sets a value to an inline table, which is not accepted",
arg,
);
+ } else {
+ got_to_value = true;
+ break;
}
- depth -= 1;
- table = t.values().next().unwrap().as_table();
}
+ got_to_value
+ };
+ if !ok {
+ bail!(
+ "--config argument `{}` was not a TOML dotted key expression (a.b.c = _)",
+ arg
+ );
}
+
+ // Rather than trying to bridge between toml_edit::Document and toml::Value, we
+ // just parse the whole argument again now that we know it has the right format.
+ let toml_v = toml::from_str(arg).with_context(|| {
+ format!("failed to parse value from --config argument `{}`", arg)
+ })?;
+
CV::from_toml(Definition::Cli, toml_v)
.with_context(|| format!("failed to convert --config argument `{}`", arg))?
};
assert_error(
config.unwrap_err(),
"\
-failed to parse --config argument `abc`
+failed to parse value from --config argument `abc` as a dotted key expression
Caused by:
- expected an equals, found eof at line 1 column 4",
+ TOML parse error at line 1, column 4
+ |
+1 | abc
+ | ^
+Unexpected `end of input`
+Expected `.` or `=`",
+ );
+
+ let config = ConfigBuilder::new().config_arg("").build_err();
+ assert_error(
+ config.unwrap_err(),
+ "--config argument `` was not a TOML dotted key expression (a.b.c = _)",
);
}
config.unwrap_err(),
"\
--config argument `a=1
-b=2` expected exactly one key=value pair, got 2 keys",
+b=2` was not a TOML dotted key expression (a.b.c = _)",
);
+}
- let config = ConfigBuilder::new().config_arg("").build_err();
+#[cargo_test]
+fn no_inline_table_value() {
+ // Disallow inline tables
+ let config = ConfigBuilder::new()
+ .config_arg("a.b={c = \"d\"}")
+ .build_err();
+ assert_error(
+ config.unwrap_err(),
+ "--config argument `a.b={c = \"d\"}` sets a value to an inline table, which is not accepted"
+ );
+}
+
+#[cargo_test]
+fn no_array_of_tables_values() {
+ // Disallow array-of-tables when not in dotted form
+ let config = ConfigBuilder::new()
+ .config_arg("[[a.b]]\nc = \"d\"")
+ .build_err();
assert_error(
config.unwrap_err(),
"\
- --config argument `` expected exactly one key=value pair, got 0 keys",
+--config argument `[[a.b]]
+c = \"d\"` was not a TOML dotted key expression (a.b.c = _)",
);
}