nu_command/network/url/
decode.rs1use nu_cmd_base::input_handler::{CellPathOnlyArgs, operate};
2use nu_engine::command_prelude::*;
3use nu_protocol::shell_error::generic::GenericError;
4
5use percent_encoding::percent_decode_str;
6
7#[derive(Clone)]
8pub struct UrlDecode;
9
10impl Command for UrlDecode {
11 fn name(&self) -> &str {
12 "url decode"
13 }
14
15 fn signature(&self) -> Signature {
16 Signature::build("url decode")
17 .input_output_types(vec![
18 (Type::String, Type::String),
19 (
20 Type::List(Box::new(Type::String)),
21 Type::List(Box::new(Type::String)),
22 ),
23 (Type::table(), Type::table()),
24 (Type::record(), Type::record()),
25 ])
26 .allow_variants_without_examples(true)
27 .rest(
28 "rest",
29 SyntaxShape::CellPath,
30 "For a data structure input, url decode strings at the given cell paths.",
31 )
32 .category(Category::Strings)
33 }
34
35 fn description(&self) -> &str {
36 "Converts a percent-encoded web safe string to a string."
37 }
38
39 fn search_terms(&self) -> Vec<&str> {
40 vec!["string", "text", "convert"]
41 }
42
43 fn run(
44 &self,
45 engine_state: &EngineState,
46 stack: &mut Stack,
47 call: &Call,
48 input: PipelineData,
49 ) -> Result<PipelineData, ShellError> {
50 let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
51 let args = CellPathOnlyArgs::from(cell_paths);
52 operate(action, args, input, call.head, engine_state.signals())
53 }
54
55 fn examples(&self) -> Vec<Example<'_>> {
56 vec![
57 Example {
58 description: "Decode a URL with escape characters.",
59 example: "'https://example.com/foo%20bar' | url decode",
60 result: Some(Value::test_string("https://example.com/foo bar")),
61 },
62 Example {
63 description: "Decode multiple URLs with escape characters in list.",
64 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",
65 result: Some(Value::list(
66 vec![
67 Value::test_string("https://example.com/foo bar"),
68 Value::test_string("https://example.com/a>b"),
69 Value::test_string("中文字/eng/12 34"),
70 ],
71 Span::test_data(),
72 )),
73 },
74 ]
75 }
76}
77
78fn action(input: &Value, _arg: &CellPathOnlyArgs, head: Span) -> Value {
79 let input_span = input.span();
80 match input {
81 Value::String { val, .. } => {
82 let val = percent_decode_str(val).decode_utf8();
83 match val {
84 Ok(val) => Value::string(val, head),
85 Err(e) => Value::error(
86 ShellError::Generic(GenericError::new(
87 "Failed to decode string",
88 e.to_string(),
89 input_span,
90 )),
91 head,
92 ),
93 }
94 }
95 Value::Error { .. } => input.clone(),
96 _ => Value::error(
97 ShellError::OnlySupportsThisInputType {
98 exp_input_type: "string".into(),
99 wrong_type: input.get_type().to_string(),
100 dst_span: head,
101 src_span: input.span(),
102 },
103 head,
104 ),
105 }
106}
107
108#[cfg(test)]
109mod test {
110 use super::*;
111
112 #[test]
113 fn test_examples() -> nu_test_support::Result {
114 nu_test_support::test().examples(UrlDecode)
115 }
116}