Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add url decode command #10611

Merged
merged 1 commit into from
Oct 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions crates/nu-command/src/default_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,7 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
HttpOptions,
Url,
UrlBuildQuery,
UrlDecode,
UrlEncode,
UrlJoin,
UrlParse,
Expand Down
122 changes: 122 additions & 0 deletions crates/nu-command/src/network/url/decode.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
use nu_cmd_base::input_handler::{operate, CellPathOnlyArgs};
use nu_engine::CallExt;
use nu_protocol::ast::{Call, CellPath};
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::Category;
use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value};
use percent_encoding::percent_decode_str;

#[derive(Clone)]
pub struct SubCommand;

impl Command for SubCommand {
fn name(&self) -> &str {
"url decode"
}

fn signature(&self) -> Signature {
Signature::build("url decode")
.input_output_types(vec![
(Type::String, Type::String),
(
Type::List(Box::new(Type::String)),
Type::List(Box::new(Type::String)),
),
(Type::Table(vec![]), Type::Table(vec![])),
(Type::Record(vec![]), Type::Record(vec![])),
])
.allow_variants_without_examples(true)
.rest(
"rest",
SyntaxShape::CellPath,
"For a data structure input, url decode strings at the given cell paths",
)
.category(Category::Strings)
}

fn usage(&self) -> &str {
"Converts a percent-encoded web safe string to a string."
}

fn search_terms(&self) -> Vec<&str> {
vec!["string", "text", "convert"]
}

fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
let args = CellPathOnlyArgs::from(cell_paths);
operate(action, args, input, call.head, engine_state.ctrlc.clone())
}

fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Decode a url with escape characters",
example: "'https://example.com/foo%20bar' | url decode",
result: Some(Value::test_string("https://example.com/foo bar")),
},
Example {
description: "Decode multiple urls with escape characters in list",
example: "['https://example.com/foo%20bar' 'https://example.com/a%3Eb' '%E4%B8%AD%E6%96%87%E5%AD%97/eng/12%2034'] | url decode",
result: Some(Value::list(
vec![
Value::test_string("https://example.com/foo bar"),
Value::test_string("https://example.com/a>b"),
Value::test_string("中文字/eng/12 34"),
],
Span::test_data(),
)),
},
]
}
}

fn action(input: &Value, _arg: &CellPathOnlyArgs, head: Span) -> Value {
let input_span = input.span();
match input {
Value::String { val, .. } => {
let val = percent_decode_str(val).decode_utf8();
match val {
Ok(val) => Value::string(val, head),
Err(e) => Value::error(
ShellError::GenericError(
"Failed to decode string".into(),
e.to_string(),
Some(input_span),
None,
Vec::new(),
),
head,
),
}
}
Value::Error { .. } => input.clone(),
_ => Value::error(
ShellError::OnlySupportsThisInputType {
exp_input_type: "string".into(),
wrong_type: input.get_type().to_string(),
dst_span: head,
src_span: input.span(),
},
head,
),
}
}

#[cfg(test)]
mod test {
use super::*;

#[test]
fn test_examples() {
use crate::test_examples;

test_examples(SubCommand {})
}
}
2 changes: 2 additions & 0 deletions crates/nu-command/src/network/url/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
mod build_query;
mod decode;
mod encode;
mod join;
mod parse;
Expand All @@ -8,6 +9,7 @@ use url::{self};

pub use self::parse::SubCommand as UrlParse;
pub use build_query::SubCommand as UrlBuildQuery;
pub use decode::SubCommand as UrlDecode;
pub use encode::SubCommand as UrlEncode;
pub use join::SubCommand as UrlJoin;
pub use url_::Url;
19 changes: 19 additions & 0 deletions crates/nu-command/tests/commands/url/decode.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use nu_test_support::nu;

#[test]
fn url_decode_simple() {
let actual = nu!(r#"'a%20b' | url decode"#);
assert_eq!(actual.out, "a b");
}

#[test]
fn url_decode_special_characters() {
let actual = nu!(r#"'%21%40%23%24%25%C2%A8%26%2A%2D%2B%3B%2C%7B%7D%5B%5D%28%29' | url decode"#);
assert_eq!(actual.out, r#"!@#$%¨&*-+;,{}[]()"#);
}

#[test]
fn url_decode_error_invalid_utf8() {
let actual = nu!(r#"'%99' | url decode"#);
assert!(actual.err.contains("invalid utf-8 sequence"));
}
1 change: 1 addition & 0 deletions crates/nu-command/tests/commands/url/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
mod decode;
mod join;
mod parse;