Skip to main content

nu_command/network/http/
http_.rs

1use nu_engine::{command_prelude::*, get_full_help};
2use nu_protocol::shell_error::generic::GenericError;
3
4use super::client::RedirectMode;
5use super::get::run_get;
6use super::post::run_post;
7
8#[derive(Clone)]
9pub struct Http;
10
11impl Command for Http {
12    fn name(&self) -> &str {
13        "http"
14    }
15
16    fn signature(&self) -> Signature {
17        Signature::build("http")
18            .input_output_types(vec![(Type::Nothing, Type::Any)])
19            // common to get more than help. Get by default
20            .optional(
21                "URL",
22                SyntaxShape::String,
23                "The URL to fetch the contents from.",
24            )
25            // post
26            .optional(
27                "data",
28                SyntaxShape::Any,
29                "The contents of the post body. Required unless part of a pipeline.",
30            )
31            .named(
32                "content-type",
33                SyntaxShape::Any,
34                "The MIME type of content to post.",
35                Some('t'),
36            )
37            // common
38            .named(
39                "user",
40                SyntaxShape::Any,
41                "The username when authenticating.",
42                Some('u'),
43            )
44            .named(
45                "password",
46                SyntaxShape::Any,
47                "The password when authenticating.",
48                Some('p'),
49            )
50            .named(
51                "max-time",
52                SyntaxShape::Duration,
53                "Max duration before timeout occurs.",
54                Some('m'),
55            )
56            .named(
57                "headers",
58                SyntaxShape::Any,
59                "Custom headers you want to add.",
60                Some('H'),
61            )
62            .switch(
63                "raw",
64                "Fetch contents as text rather than a table.",
65                Some('r'),
66            )
67            .switch(
68                "insecure",
69                "Allow insecure server connections when using SSL.",
70                Some('k'),
71            )
72            .switch(
73                "full",
74                "Returns the record, containing metainformation about the exchange in addition to \
75                 the response.
76                Returning record fields:
77                - urls: list of url redirects this command had to make to get to the destination
78                - headers.request: list of headers passed when doing the request
79                - headers.response: list of received headers
80                - body: the http body of the response
81                - status: the http status of the response.",
82                Some('f'),
83            )
84            .switch(
85                "allow-errors",
86                "Do not fail if the server returns an error code.",
87                Some('e'),
88            )
89            .param(
90                Flag::new("redirect-mode")
91                    .short('R')
92                    .arg(SyntaxShape::String)
93                    .desc(
94                        "What to do when encountering redirects. Default: 'follow'. Valid \
95                         options: 'follow' ('f'), 'manual' ('m'), 'error' ('e').",
96                    )
97                    .completion(Completion::new_list(RedirectMode::MODES)),
98            )
99            .category(Category::Network)
100    }
101
102    fn description(&self) -> &str {
103        "Various commands for working with http methods."
104    }
105
106    fn extra_description(&self) -> &str {
107        "Without a subcommand but with a URL provided, it performs a GET request by default or a POST request if data is provided. You can use one of the following subcommands. Using this command as-is will only display this help message."
108    }
109
110    fn search_terms(&self) -> Vec<&str> {
111        vec![
112            "network", "fetch", "pull", "request", "download", "curl", "wget",
113        ]
114    }
115
116    fn run(
117        &self,
118        engine_state: &EngineState,
119        stack: &mut Stack,
120        call: &Call,
121        input: PipelineData,
122    ) -> Result<PipelineData, ShellError> {
123        let url = call.opt::<Spanned<String>>(engine_state, stack, 0)?;
124        let data: Option<Value> = call.opt::<Value>(engine_state, stack, 1)?;
125
126        // prefer stricter calls over aliasing with variables
127        if let Some(Spanned { item: method, span }) = &url
128            && let method @ ("delete" | "get" | "head" | "options" | "patch" | "post" | "put") =
129                method.to_lowercase().as_str()
130        {
131            return Err(ShellError::Generic(
132                GenericError::new(
133                    "Invalid command construction",
134                    format!(
135                        "Using {method:?} dynamically is bad command construction. You are providing it to the `url` positional argument of `http`"
136                    ),
137                    *span,
138                )
139                .with_help(format!("Prefer to use `http {method}` directly")),
140            ));
141        }
142
143        match (url, data) {
144            (Some(_), Some(_)) => run_post(engine_state, stack, call, input),
145            (Some(_), None) => run_get(engine_state, stack, call, input),
146            (None, Some(_)) => Err(ShellError::NushellFailed {
147                msg: (String::from("Default verb is get with a payload. Impossible state")),
148            }),
149            (None, None) => Ok(Value::string(
150                get_full_help(self, engine_state, stack, call.head),
151                call.head,
152            )
153            .into_pipeline_data()),
154        }
155    }
156
157    fn examples(&self) -> Vec<Example<'_>> {
158        vec![
159            Example {
160                description: "Get content from example.com with default verb.",
161                example: "http https://www.example.com",
162                result: None,
163            },
164            Example {
165                description: "Post content to example.com with default verb.",
166                example: "http https://www.example.com 'body'",
167                result: None,
168            },
169            Example {
170                description: "Get content from example.com with explicit verb.",
171                example: "http get https://www.example.com",
172                result: None,
173            },
174        ]
175    }
176}