1use crate::network::http::client::{
2 HttpBody, RequestFlags, check_response_redirection, http_client, http_parse_redirect_mode,
3 http_parse_url, request_add_authorization_header, request_add_custom_headers,
4 request_handle_response, request_set_timeout, send_request,
5};
6use nu_engine::command_prelude::*;
7
8#[derive(Clone)]
9pub struct HttpDelete;
10
11impl Command for HttpDelete {
12 fn name(&self) -> &str {
13 "http delete"
14 }
15
16 fn signature(&self) -> Signature {
17 Signature::build("http delete")
18 .input_output_types(vec![(Type::Any, Type::Any)])
19 .allow_variants_without_examples(true)
20 .required(
21 "URL",
22 SyntaxShape::String,
23 "The URL to fetch the contents from.",
24 )
25 .named(
26 "user",
27 SyntaxShape::Any,
28 "the username when authenticating",
29 Some('u'),
30 )
31 .named(
32 "password",
33 SyntaxShape::Any,
34 "the password when authenticating",
35 Some('p'),
36 )
37 .named("data", SyntaxShape::Any, "the content to post", Some('d'))
38 .named(
39 "content-type",
40 SyntaxShape::Any,
41 "the MIME type of content to post",
42 Some('t'),
43 )
44 .named(
45 "max-time",
46 SyntaxShape::Duration,
47 "max duration before timeout occurs",
48 Some('m'),
49 )
50 .named(
51 "headers",
52 SyntaxShape::Any,
53 "custom headers you want to add ",
54 Some('H'),
55 )
56 .switch(
57 "raw",
58 "fetch contents as text rather than a table",
59 Some('r'),
60 )
61 .switch(
62 "insecure",
63 "allow insecure server connections when using SSL",
64 Some('k'),
65 )
66 .switch(
67 "full",
68 "returns the full response instead of only the body",
69 Some('f'),
70 )
71 .switch(
72 "allow-errors",
73 "do not fail if the server returns an error code",
74 Some('e'),
75 ).named(
76 "redirect-mode",
77 SyntaxShape::String,
78 "What to do when encountering redirects. Default: 'follow'. Valid options: 'follow' ('f'), 'manual' ('m'), 'error' ('e').",
79 Some('R')
80 )
81 .filter()
82 .category(Category::Network)
83 }
84
85 fn description(&self) -> &str {
86 "Delete the specified resource."
87 }
88
89 fn extra_description(&self) -> &str {
90 "Performs HTTP DELETE operation."
91 }
92
93 fn search_terms(&self) -> Vec<&str> {
94 vec!["network", "request", "curl", "wget"]
95 }
96
97 fn run(
98 &self,
99 engine_state: &EngineState,
100 stack: &mut Stack,
101 call: &Call,
102 input: PipelineData,
103 ) -> Result<PipelineData, ShellError> {
104 run_delete(engine_state, stack, call, input)
105 }
106
107 fn examples(&self) -> Vec<Example> {
108 vec![
109 Example {
110 description: "http delete from example.com",
111 example: "http delete https://www.example.com",
112 result: None,
113 },
114 Example {
115 description: "http delete from example.com, with username and password",
116 example: "http delete --user myuser --password mypass https://www.example.com",
117 result: None,
118 },
119 Example {
120 description: "http delete from example.com, with custom header using a record",
121 example: "http delete --headers {my-header-key: my-header-value} https://www.example.com",
122 result: None,
123 },
124 Example {
125 description: "http delete from example.com, with custom header using a list",
126 example: "http delete --headers [my-header-key-A my-header-value-A my-header-key-B my-header-value-B] https://www.example.com",
127 result: None,
128 },
129 Example {
130 description: "http delete from example.com, with body",
131 example: "http delete --data 'body' https://www.example.com",
132 result: None,
133 },
134 Example {
135 description: "http delete from example.com, with JSON body",
136 example: "http delete --content-type application/json --data { field: value } https://www.example.com",
137 result: None,
138 },
139 Example {
140 description: "Perform an HTTP delete with JSON content from a pipeline to example.com",
141 example: "open foo.json | http delete https://www.example.com",
142 result: None,
143 },
144 ]
145 }
146}
147
148struct Arguments {
149 url: Value,
150 headers: Option<Value>,
151 data: HttpBody,
152 content_type: Option<String>,
153 raw: bool,
154 insecure: bool,
155 user: Option<String>,
156 password: Option<String>,
157 timeout: Option<Value>,
158 full: bool,
159 allow_errors: bool,
160 redirect: Option<Spanned<String>>,
161}
162
163fn run_delete(
164 engine_state: &EngineState,
165 stack: &mut Stack,
166 call: &Call,
167 input: PipelineData,
168) -> Result<PipelineData, ShellError> {
169 let (data, maybe_metadata) = call
170 .get_flag::<Value>(engine_state, stack, "data")?
171 .map(|v| (HttpBody::Value(v), None))
172 .unwrap_or_else(|| match input {
173 PipelineData::Value(v, metadata) => (HttpBody::Value(v), metadata),
174 PipelineData::ByteStream(byte_stream, metadata) => {
175 (HttpBody::ByteStream(byte_stream), metadata)
176 }
177 _ => (HttpBody::None, None),
178 });
179 let content_type = call
180 .get_flag(engine_state, stack, "content-type")?
181 .or_else(|| maybe_metadata.and_then(|m| m.content_type));
182
183 let args = Arguments {
184 url: call.req(engine_state, stack, 0)?,
185 headers: call.get_flag(engine_state, stack, "headers")?,
186 data,
187 content_type,
188 raw: call.has_flag(engine_state, stack, "raw")?,
189 insecure: call.has_flag(engine_state, stack, "insecure")?,
190 user: call.get_flag(engine_state, stack, "user")?,
191 password: call.get_flag(engine_state, stack, "password")?,
192 timeout: call.get_flag(engine_state, stack, "max-time")?,
193 full: call.has_flag(engine_state, stack, "full")?,
194 allow_errors: call.has_flag(engine_state, stack, "allow-errors")?,
195 redirect: call.get_flag(engine_state, stack, "redirect-mode")?,
196 };
197
198 helper(engine_state, stack, call, args)
199}
200
201fn helper(
204 engine_state: &EngineState,
205 stack: &mut Stack,
206 call: &Call,
207 args: Arguments,
208) -> Result<PipelineData, ShellError> {
209 let span = args.url.span();
210 let (requested_url, _) = http_parse_url(call, span, args.url)?;
211 let redirect_mode = http_parse_redirect_mode(args.redirect)?;
212
213 let client = http_client(args.insecure, redirect_mode, engine_state, stack)?;
214 let mut request = client.delete(&requested_url);
215
216 request = request_set_timeout(args.timeout, request)?;
217 request = request_add_authorization_header(args.user, args.password, request);
218 request = request_add_custom_headers(args.headers, request)?;
219
220 let response = send_request(
221 engine_state,
222 request.clone(),
223 args.data,
224 args.content_type,
225 call.head,
226 engine_state.signals(),
227 );
228
229 let request_flags = RequestFlags {
230 raw: args.raw,
231 full: args.full,
232 allow_errors: args.allow_errors,
233 };
234
235 check_response_redirection(redirect_mode, span, &response)?;
236 request_handle_response(
237 engine_state,
238 stack,
239 span,
240 &requested_url,
241 request_flags,
242 response,
243 request,
244 )
245}
246
247#[cfg(test)]
248mod tests {
249 use super::*;
250
251 #[test]
252 fn test_examples() {
253 use crate::test_examples;
254
255 test_examples(HttpDelete {})
256 }
257}