use percent_encoding::percent_decode_str;
use toml::Value;
use crate::env::parse_env_value;
pub fn query_to_overrides(query: &str) -> Vec<(String, Value)> {
let query = query.strip_prefix('?').unwrap_or(query);
let mut overrides = Vec::new();
for pair in query.split('&') {
if pair.is_empty() {
continue;
}
let (raw_key, raw_value) = match pair.split_once('=') {
Some((k, v)) => (k, v),
None => (pair, ""),
};
let key = percent_decode_str(raw_key).decode_utf8_lossy().into_owned();
if key.is_empty() {
continue;
}
let value = percent_decode_str(raw_value)
.decode_utf8_lossy()
.into_owned();
overrides.push((key, parse_env_value(&value)));
}
overrides
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn simple_string() {
let o = query_to_overrides("host=example.com");
assert_eq!(o.len(), 1);
assert_eq!(o[0].0, "host");
assert_eq!(o[0].1, Value::String("example.com".into()));
}
#[test]
fn integer_value() {
let o = query_to_overrides("port=8080");
assert_eq!(o[0].1, Value::Integer(8080));
}
#[test]
fn bool_value() {
let o = query_to_overrides("debug=true");
assert_eq!(o[0].1, Value::Boolean(true));
}
#[test]
fn float_value() {
let o = query_to_overrides("rate=1.5");
assert_eq!(o[0].1, Value::Float(1.5));
}
#[test]
fn nested_dotted_key() {
let o = query_to_overrides("database.url=pg://prod");
assert_eq!(o[0].0, "database.url");
assert_eq!(o[0].1, Value::String("pg://prod".into()));
}
#[test]
fn percent_decoding() {
let o = query_to_overrides("database.url=pg%3A%2F%2Fprod%3Fssl%3Dtrue");
assert_eq!(o[0].1, Value::String("pg://prod?ssl=true".into()));
}
#[test]
fn leading_question_mark_stripped() {
let o = query_to_overrides("?port=3000");
assert_eq!(o[0].0, "port");
assert_eq!(o[0].1, Value::Integer(3000));
}
#[test]
fn multiple_params() {
let o = query_to_overrides("host=x&port=3000&debug=true");
assert_eq!(o.len(), 3);
assert_eq!(o[0].0, "host");
assert_eq!(o[1].0, "port");
assert_eq!(o[2].0, "debug");
}
#[test]
fn empty_value() {
let o = query_to_overrides("key=");
assert_eq!(o[0].1, Value::String("".into()));
}
#[test]
fn no_equals_sign() {
let o = query_to_overrides("flag");
assert_eq!(o[0].0, "flag");
assert_eq!(o[0].1, Value::String("".into()));
}
#[test]
fn empty_string() {
let o = query_to_overrides("");
assert!(o.is_empty());
}
#[test]
fn bare_ampersands_skipped() {
let o = query_to_overrides("&&port=80&&");
assert_eq!(o.len(), 1);
assert_eq!(o[0].0, "port");
}
#[test]
fn empty_key_skipped() {
let o = query_to_overrides("=value");
assert!(o.is_empty());
}
#[test]
fn last_value_wins_for_duplicate_keys() {
let o = query_to_overrides("port=3000&port=5000");
assert_eq!(o.len(), 2);
assert_eq!(o[0].1, Value::Integer(3000));
assert_eq!(o[1].1, Value::Integer(5000));
}
#[test]
fn percent_encoded_key() {
let o = query_to_overrides("database%2Eurl=pg://");
assert_eq!(o[0].0, "database.url");
}
}