diff --git a/crates/nu-command/src/network/url/build_query.rs b/crates/nu-command/src/network/url/build_query.rs index 8b563a2bbd..11fd9aba94 100644 --- a/crates/nu-command/src/network/url/build_query.rs +++ b/crates/nu-command/src/network/url/build_query.rs @@ -1,5 +1,7 @@ use nu_engine::command_prelude::*; +use super::query::record_to_query_string; + #[derive(Clone)] pub struct SubCommand; @@ -38,12 +40,12 @@ impl Command for SubCommand { result: Some(Value::test_string("foo=1&bar=2")), }, Example { - description: "Outputs a query string representing the contents of this record", + description: "Outputs a query string representing the contents of this record, with a value that needs to be url-encoded", example: r#"{a:"AT&T", b: "AT T"} | url build-query"#, result: Some(Value::test_string("a=AT%26T&b=AT+T")), }, Example { - description: "Outputs a query string representing the contents of this record", + description: "Outputs a query string representing the contents of this record, \"exploding\" the list into multiple parameters", example: r#"{a: ["one", "two"], b: "three"} | url build-query"#, result: Some(Value::test_string("a=one&a=two&b=three")), }, @@ -68,48 +70,7 @@ fn to_url(input: PipelineData, head: Span) -> Result { .map(move |value| { let span = value.span(); match value { - Value::Record { ref val, .. } => { - let mut row_vec = vec![]; - for (k, v) in &**val { - match v { - Value::List { ref vals, .. } => { - for v_item in vals { - row_vec.push(( - k.clone(), - v_item.coerce_string().map_err(|_| { - ShellError::UnsupportedInput { - msg: "Expected a record with list of string values" - .to_string(), - input: "value originates from here".into(), - msg_span: head, - input_span: span, - } - })?, - )); - } - } - _ => row_vec.push(( - k.clone(), - v.coerce_string() - .map_err(|_| ShellError::UnsupportedInput { - msg: - "Expected a record with string or list of string values" - .to_string(), - input: "value originates from here".into(), - msg_span: head, - input_span: span, - })?, - )), - } - } - - serde_urlencoded::to_string(row_vec).map_err(|_| ShellError::CantConvert { - to_type: "URL".into(), - from_type: value.get_type().to_string(), - span: head, - help: None, - }) - } + Value::Record { ref val, .. } => record_to_query_string(val, span, head), // Propagate existing errors Value::Error { error, .. } => Err(*error), other => Err(ShellError::UnsupportedInput { diff --git a/crates/nu-command/src/network/url/join.rs b/crates/nu-command/src/network/url/join.rs index 9be5de38e6..3b52b1d6b0 100644 --- a/crates/nu-command/src/network/url/join.rs +++ b/crates/nu-command/src/network/url/join.rs @@ -1,5 +1,7 @@ use nu_engine::command_prelude::*; +use super::query::record_to_query_string; + #[derive(Clone)] pub struct SubCommand; @@ -27,7 +29,7 @@ impl Command for SubCommand { fn examples(&self) -> Vec { vec![ Example { - description: "Outputs a url representing the contents of this record", + description: "Outputs a url representing the contents of this record, `params` and `query` fields must be equivalent", example: r#"{ "scheme": "http", "username": "", @@ -47,6 +49,21 @@ impl Command for SubCommand { "http://www.pixiv.net/member_illust.php?mode=medium&illust_id=99260204", )), }, + Example { + description: "Outputs a url representing the contents of this record, \"exploding\" the list in `params` into multiple parameters", + example: r#"{ + "scheme": "http", + "username": "user", + "password": "pwd", + "host": "www.pixiv.net", + "port": "1234", + "params": {a: ["one", "two"], b: "three"}, + "fragment": "" + } | url join"#, + result: Some(Value::test_string( + "http://user:pwd@www.pixiv.net:1234?a=one&a=two&b=three", + )), + }, Example { description: "Outputs a url representing the contents of this record", example: r#"{ @@ -178,16 +195,8 @@ impl UrlComponents { if key == "params" { return match value { - Value::Record { val, .. } => { - let mut qs = val - .into_owned() - .into_iter() - .map(|(k, v)| match v.coerce_into_string() { - Ok(val) => Ok(format!("{k}={val}")), - Err(err) => Err(err), - }) - .collect::, ShellError>>()? - .join("&"); + Value::Record { ref val, .. } => { + let mut qs = record_to_query_string(val, value_span, span)?; qs = if !qs.trim().is_empty() { format!("?{qs}") diff --git a/crates/nu-command/src/network/url/mod.rs b/crates/nu-command/src/network/url/mod.rs index 2ca22e128c..3c4a4ac970 100644 --- a/crates/nu-command/src/network/url/mod.rs +++ b/crates/nu-command/src/network/url/mod.rs @@ -3,6 +3,7 @@ mod decode; mod encode; mod join; mod parse; +mod query; mod url_; pub use self::parse::SubCommand as UrlParse; diff --git a/crates/nu-command/src/network/url/query.rs b/crates/nu-command/src/network/url/query.rs new file mode 100644 index 0000000000..fdedb73e0a --- /dev/null +++ b/crates/nu-command/src/network/url/query.rs @@ -0,0 +1,44 @@ +use nu_protocol::{Record, ShellError, Span, Type, Value}; + +pub fn record_to_query_string( + record: &Record, + span: Span, + head: Span, +) -> Result { + let mut row_vec = vec![]; + for (k, v) in record { + match v { + Value::List { ref vals, .. } => { + for v_item in vals { + row_vec.push(( + k.as_str(), + v_item + .coerce_str() + .map_err(|_| ShellError::UnsupportedInput { + msg: "Expected a record with list of string values".to_string(), + input: "value originates from here".into(), + msg_span: head, + input_span: span, + })?, + )); + } + } + _ => row_vec.push(( + k.as_str(), + v.coerce_str().map_err(|_| ShellError::UnsupportedInput { + msg: "Expected a record with string or list of string values".to_string(), + input: "value originates from here".into(), + msg_span: head, + input_span: span, + })?, + )), + } + } + + serde_urlencoded::to_string(row_vec).map_err(|_| ShellError::CantConvert { + to_type: "URL".into(), + from_type: Type::record().to_string(), + span: head, + help: None, + }) +}