mirror of
https://github.com/nushell/nushell
synced 2024-12-26 13:03:07 +00:00
feat: added multiple options to http commands (#8571)
# Description All `http` commands now have a `-f` flag which for now contains the `headers`, `body` and `status` fields (we can later add stuff like `is-redirect` or `cookies`). ![image](https://user-images.githubusercontent.com/3835355/227048504-6686445d-ad2e-4f5d-905d-e71b3a4b81a6.png) *Try it yourself* ``` http get http://mockbin.org/bin/630069dc-2c09-483a-a484-672561b7de14 http get -f http://mockbin.org/bin/630069dc-2c09-483a-a484-672561b7de14 ``` The `http` commands can also now use the `-e` flag, which stands for `--allow-errors`. When the status code is `>= 400`, it will still allow you to interpret it like a normal response. ![image](https://user-images.githubusercontent.com/3835355/227047790-b9f5a25f-2c0d-4741-881f-4189b23e4ef6.png) *Try it yourself* ``` http get http://mockbin.org/bin/2ebd3d27-bdc2-4ee8-b042-0bc2c0d1ad2a # should fail like usual http get -e http://mockbin.org/bin/2ebd3d27-bdc2-4ee8-b042-0bc2c0d1ad2a # will return the body http get -e -f http://mockbin.org/bin/2ebd3d27-bdc2-4ee8-b042-0bc2c0d1ad2a # will let you see the full response ``` # User-Facing Changes - Adds `-f` (`--full`) to all `http` commands - Adds `-e` (--allow-errors) to all `http` commands
This commit is contained in:
parent
403bf1a734
commit
ec5396a352
8 changed files with 412 additions and 118 deletions
|
@ -132,17 +132,27 @@ pub fn request_add_authorization_header(
|
|||
request
|
||||
}
|
||||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum ShellErrorOrRequestError {
|
||||
ShellError(ShellError),
|
||||
RequestError(String, Error),
|
||||
}
|
||||
|
||||
fn wrap_shell_error(err: ShellError) -> ShellErrorOrRequestError {
|
||||
ShellErrorOrRequestError::ShellError(err)
|
||||
}
|
||||
|
||||
pub fn send_request(
|
||||
request: Request,
|
||||
span: Span,
|
||||
body: Option<Value>,
|
||||
content_type: Option<String>,
|
||||
) -> Result<Response, ShellError> {
|
||||
) -> Result<Response, ShellErrorOrRequestError> {
|
||||
let request_url = request.url().to_string();
|
||||
let error_handler = |err: Error| -> ShellErrorOrRequestError {
|
||||
ShellErrorOrRequestError::RequestError(request_url, err)
|
||||
};
|
||||
if body.is_none() {
|
||||
return request
|
||||
.call()
|
||||
.map_err(|err| handle_response_error(span, &request_url, err));
|
||||
return request.call().map_err(error_handler);
|
||||
}
|
||||
let body = body.expect("Should never be none.");
|
||||
|
||||
|
@ -152,23 +162,18 @@ pub fn send_request(
|
|||
_ => BodyType::Unknown,
|
||||
};
|
||||
match body {
|
||||
Value::Binary { val, .. } => request
|
||||
.send_bytes(&val)
|
||||
.map_err(|err| handle_response_error(span, &request_url, err)),
|
||||
Value::String { val, .. } => request
|
||||
.send_string(&val)
|
||||
.map_err(|err| handle_response_error(span, &request_url, err)),
|
||||
Value::Binary { val, .. } => request.send_bytes(&val).map_err(error_handler),
|
||||
Value::String { val, .. } => request.send_string(&val).map_err(error_handler),
|
||||
Value::Record { .. } if body_type == BodyType::Json => {
|
||||
let data = value_to_json_value(&body)?;
|
||||
request
|
||||
.send_json(data)
|
||||
.map_err(|err| handle_response_error(span, &request_url, err))
|
||||
let data = value_to_json_value(&body);
|
||||
request.send_json(data).map_err(error_handler)
|
||||
}
|
||||
Value::Record { cols, vals, .. } if body_type == BodyType::Form => {
|
||||
let mut data: Vec<(String, String)> = Vec::with_capacity(cols.len());
|
||||
|
||||
for (col, val) in cols.iter().zip(vals.iter()) {
|
||||
data.push((col.clone(), val.as_string()?))
|
||||
let val_string = val.as_string().map_err(wrap_shell_error)?;
|
||||
data.push((col.clone(), val_string))
|
||||
}
|
||||
|
||||
let data = data
|
||||
|
@ -176,30 +181,35 @@ pub fn send_request(
|
|||
.map(|(a, b)| (a.as_str(), b.as_str()))
|
||||
.collect::<Vec<(&str, &str)>>();
|
||||
|
||||
request
|
||||
.send_form(&data[..])
|
||||
.map_err(|err| handle_response_error(span, &request_url, err))
|
||||
request.send_form(&data[..]).map_err(error_handler)
|
||||
}
|
||||
Value::List { vals, .. } if body_type == BodyType::Form => {
|
||||
if vals.len() % 2 != 0 {
|
||||
return Err(ShellError::IOError("unsupported body input".into()));
|
||||
return Err(ShellErrorOrRequestError::ShellError(ShellError::IOError(
|
||||
"unsupported body input".into(),
|
||||
)));
|
||||
}
|
||||
|
||||
let data = vals
|
||||
.chunks(2)
|
||||
.map(|it| Ok((it[0].as_string()?, it[1].as_string()?)))
|
||||
.collect::<Result<Vec<(String, String)>, ShellError>>()?;
|
||||
.map(|it| {
|
||||
Ok((
|
||||
it[0].as_string().map_err(wrap_shell_error)?,
|
||||
it[1].as_string().map_err(wrap_shell_error)?,
|
||||
))
|
||||
})
|
||||
.collect::<Result<Vec<(String, String)>, ShellErrorOrRequestError>>()?;
|
||||
|
||||
let data = data
|
||||
.iter()
|
||||
.map(|(a, b)| (a.as_str(), b.as_str()))
|
||||
.collect::<Vec<(&str, &str)>>();
|
||||
|
||||
request
|
||||
.send_form(&data)
|
||||
.map_err(|err| handle_response_error(span, &request_url, err))
|
||||
request.send_form(&data).map_err(error_handler)
|
||||
}
|
||||
_ => Err(ShellError::IOError("unsupported body input".into())),
|
||||
_ => Err(ShellErrorOrRequestError::ShellError(ShellError::IOError(
|
||||
"unsupported body input".into(),
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -317,107 +327,203 @@ fn handle_response_error(span: Span, requested_url: &str, response_err: Error) -
|
|||
}
|
||||
}
|
||||
|
||||
pub struct RequestFlags {
|
||||
pub allow_errors: bool,
|
||||
pub raw: bool,
|
||||
pub full: bool,
|
||||
}
|
||||
|
||||
#[allow(clippy::needless_return)]
|
||||
fn transform_response_using_content_type(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
span: Span,
|
||||
requested_url: &str,
|
||||
flags: &RequestFlags,
|
||||
resp: Response,
|
||||
content_type: &str,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let content_type = mime::Mime::from_str(content_type).map_err(|_| {
|
||||
ShellError::GenericError(
|
||||
format!("MIME type unknown: {content_type}"),
|
||||
"".to_string(),
|
||||
None,
|
||||
Some("given unknown MIME type".to_string()),
|
||||
Vec::new(),
|
||||
)
|
||||
})?;
|
||||
let ext = match (content_type.type_(), content_type.subtype()) {
|
||||
(mime::TEXT, mime::PLAIN) => {
|
||||
let path_extension = url::Url::parse(requested_url)
|
||||
.map_err(|_| {
|
||||
ShellError::GenericError(
|
||||
format!("Cannot parse URL: {requested_url}"),
|
||||
"".to_string(),
|
||||
None,
|
||||
Some("cannot parse".to_string()),
|
||||
Vec::new(),
|
||||
)
|
||||
})?
|
||||
.path_segments()
|
||||
.and_then(|segments| segments.last())
|
||||
.and_then(|name| if name.is_empty() { None } else { Some(name) })
|
||||
.and_then(|name| {
|
||||
PathBuf::from(name)
|
||||
.extension()
|
||||
.map(|name| name.to_string_lossy().to_string())
|
||||
});
|
||||
path_extension
|
||||
}
|
||||
_ => Some(content_type.subtype().to_string()),
|
||||
};
|
||||
|
||||
let output = response_to_buffer(resp, engine_state, span);
|
||||
if flags.raw {
|
||||
return Ok(output);
|
||||
} else if let Some(ext) = ext {
|
||||
return match engine_state.find_decl(format!("from {ext}").as_bytes(), &[]) {
|
||||
Some(converter_id) => engine_state.get_decl(converter_id).run(
|
||||
engine_state,
|
||||
stack,
|
||||
&Call::new(span),
|
||||
output,
|
||||
),
|
||||
None => Ok(output),
|
||||
};
|
||||
} else {
|
||||
return Ok(output);
|
||||
};
|
||||
}
|
||||
|
||||
fn request_handle_response_content(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
span: Span,
|
||||
requested_url: &str,
|
||||
flags: RequestFlags,
|
||||
resp: Response,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let response_headers: Option<PipelineData> = if flags.full {
|
||||
let headers_raw = request_handle_response_headers_raw(span, &resp)?;
|
||||
Some(headers_raw)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let response_status = resp.status();
|
||||
let content_type = resp.header("content-type").map(|s| s.to_owned());
|
||||
let formatted_content = match content_type {
|
||||
Some(content_type) => transform_response_using_content_type(
|
||||
engine_state,
|
||||
stack,
|
||||
span,
|
||||
requested_url,
|
||||
&flags,
|
||||
resp,
|
||||
&content_type,
|
||||
),
|
||||
None => Ok(response_to_buffer(resp, engine_state, span)),
|
||||
};
|
||||
if flags.full {
|
||||
let full_response = Value::Record {
|
||||
cols: vec![
|
||||
"headers".to_string(),
|
||||
"body".to_string(),
|
||||
"status".to_string(),
|
||||
],
|
||||
vals: vec![
|
||||
match response_headers {
|
||||
Some(headers) => headers.into_value(span),
|
||||
None => Value::nothing(span),
|
||||
},
|
||||
formatted_content?.into_value(span),
|
||||
Value::int(response_status as i64, span),
|
||||
],
|
||||
span,
|
||||
}
|
||||
.into_pipeline_data();
|
||||
Ok(full_response)
|
||||
} else {
|
||||
Ok(formatted_content?)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn request_handle_response(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
span: Span,
|
||||
requested_url: &String,
|
||||
raw: bool,
|
||||
response: Result<Response, ShellError>,
|
||||
requested_url: &str,
|
||||
flags: RequestFlags,
|
||||
response: Result<Response, ShellErrorOrRequestError>,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
match response {
|
||||
Ok(resp) => match resp.header("content-type") {
|
||||
Some(content_type) => {
|
||||
let content_type = mime::Mime::from_str(content_type).map_err(|_| {
|
||||
ShellError::GenericError(
|
||||
format!("MIME type unknown: {content_type}"),
|
||||
"".to_string(),
|
||||
None,
|
||||
Some("given unknown MIME type".to_string()),
|
||||
Vec::new(),
|
||||
)
|
||||
})?;
|
||||
let ext = match (content_type.type_(), content_type.subtype()) {
|
||||
(mime::TEXT, mime::PLAIN) => {
|
||||
let path_extension = url::Url::parse(requested_url)
|
||||
.map_err(|_| {
|
||||
ShellError::GenericError(
|
||||
format!("Cannot parse URL: {requested_url}"),
|
||||
"".to_string(),
|
||||
None,
|
||||
Some("cannot parse".to_string()),
|
||||
Vec::new(),
|
||||
)
|
||||
})?
|
||||
.path_segments()
|
||||
.and_then(|segments| segments.last())
|
||||
.and_then(|name| if name.is_empty() { None } else { Some(name) })
|
||||
.and_then(|name| {
|
||||
PathBuf::from(name)
|
||||
.extension()
|
||||
.map(|name| name.to_string_lossy().to_string())
|
||||
});
|
||||
path_extension
|
||||
}
|
||||
_ => Some(content_type.subtype().to_string()),
|
||||
};
|
||||
|
||||
let output = response_to_buffer(resp, engine_state, span);
|
||||
|
||||
if raw {
|
||||
return Ok(output);
|
||||
}
|
||||
|
||||
if let Some(ext) = ext {
|
||||
match engine_state.find_decl(format!("from {ext}").as_bytes(), &[]) {
|
||||
Some(converter_id) => engine_state.get_decl(converter_id).run(
|
||||
Ok(resp) => {
|
||||
request_handle_response_content(engine_state, stack, span, requested_url, flags, resp)
|
||||
}
|
||||
Err(e) => match e {
|
||||
ShellErrorOrRequestError::ShellError(e) => Err(e),
|
||||
ShellErrorOrRequestError::RequestError(_, e) => {
|
||||
if flags.allow_errors {
|
||||
if let Error::Status(_, resp) = e {
|
||||
Ok(request_handle_response_content(
|
||||
engine_state,
|
||||
stack,
|
||||
&Call::new(span),
|
||||
output,
|
||||
),
|
||||
None => Ok(output),
|
||||
span,
|
||||
requested_url,
|
||||
flags,
|
||||
resp,
|
||||
)?)
|
||||
} else {
|
||||
Err(handle_response_error(span, requested_url, e))
|
||||
}
|
||||
} else {
|
||||
Ok(output)
|
||||
Err(handle_response_error(span, requested_url, e))
|
||||
}
|
||||
}
|
||||
None => Ok(response_to_buffer(resp, engine_state, span)),
|
||||
},
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn request_handle_response_headers_raw(
|
||||
span: Span,
|
||||
response: &Response,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let cols = response.headers_names();
|
||||
|
||||
let mut vals = Vec::with_capacity(cols.len());
|
||||
for key in &cols {
|
||||
match response.header(key) {
|
||||
// match value.to_str() {
|
||||
Some(str_value) => vals.push(Value::String {
|
||||
val: str_value.to_string(),
|
||||
span,
|
||||
}),
|
||||
None => {
|
||||
return Err(ShellError::GenericError(
|
||||
"Failure when converting header value".to_string(),
|
||||
"".to_string(),
|
||||
None,
|
||||
Some("Failure when converting header value".to_string()),
|
||||
Vec::new(),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Value::Record { cols, vals, span }.into_pipeline_data())
|
||||
}
|
||||
|
||||
pub fn request_handle_response_headers(
|
||||
span: Span,
|
||||
response: Result<Response, ShellError>,
|
||||
response: Result<Response, ShellErrorOrRequestError>,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
match response {
|
||||
Ok(resp) => {
|
||||
let cols = resp.headers_names();
|
||||
|
||||
let mut vals = Vec::with_capacity(cols.len());
|
||||
for key in &cols {
|
||||
match resp.header(key) {
|
||||
// match value.to_str() {
|
||||
Some(str_value) => vals.push(Value::String {
|
||||
val: str_value.to_string(),
|
||||
span,
|
||||
}),
|
||||
None => {
|
||||
return Err(ShellError::GenericError(
|
||||
"Failure when converting header value".to_string(),
|
||||
"".to_string(),
|
||||
None,
|
||||
Some("Failure when converting header value".to_string()),
|
||||
Vec::new(),
|
||||
))
|
||||
}
|
||||
}
|
||||
Ok(resp) => request_handle_response_headers_raw(span, &resp),
|
||||
Err(e) => match e {
|
||||
ShellErrorOrRequestError::ShellError(e) => Err(e),
|
||||
ShellErrorOrRequestError::RequestError(requested_url, e) => {
|
||||
Err(handle_response_error(span, &requested_url, e))
|
||||
}
|
||||
|
||||
Ok(Value::Record { cols, vals, span }.into_pipeline_data())
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,8 @@ use crate::network::http::client::{
|
|||
request_handle_response, request_set_timeout, send_request,
|
||||
};
|
||||
|
||||
use super::client::RequestFlags;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SubCommand;
|
||||
|
||||
|
@ -68,6 +70,16 @@ impl Command for SubCommand {
|
|||
"allow insecure server connections when using SSL",
|
||||
Some('k'),
|
||||
)
|
||||
.switch(
|
||||
"full",
|
||||
"returns the full response instead of only the body",
|
||||
Some('f'),
|
||||
)
|
||||
.switch(
|
||||
"allow-errors",
|
||||
"do not fail if the server returns an error code",
|
||||
Some('e'),
|
||||
)
|
||||
.filter()
|
||||
.category(Category::Network)
|
||||
}
|
||||
|
@ -136,6 +148,8 @@ struct Arguments {
|
|||
user: Option<String>,
|
||||
password: Option<String>,
|
||||
timeout: Option<Value>,
|
||||
full: bool,
|
||||
allow_errors: bool,
|
||||
}
|
||||
|
||||
fn run_delete(
|
||||
|
@ -154,6 +168,8 @@ fn run_delete(
|
|||
user: call.get_flag(engine_state, stack, "user")?,
|
||||
password: call.get_flag(engine_state, stack, "password")?,
|
||||
timeout: call.get_flag(engine_state, stack, "max-time")?,
|
||||
full: call.has_flag("full"),
|
||||
allow_errors: call.has_flag("allow-errors"),
|
||||
};
|
||||
|
||||
helper(engine_state, stack, call, args)
|
||||
|
@ -177,14 +193,20 @@ fn helper(
|
|||
request = request_add_authorization_header(args.user, args.password, request);
|
||||
request = request_add_custom_headers(args.headers, request)?;
|
||||
|
||||
let response = send_request(request, span, args.data, args.content_type);
|
||||
let response = send_request(request, args.data, args.content_type);
|
||||
|
||||
let request_flags = RequestFlags {
|
||||
raw: args.raw,
|
||||
full: args.full,
|
||||
allow_errors: args.allow_errors,
|
||||
};
|
||||
|
||||
request_handle_response(
|
||||
engine_state,
|
||||
stack,
|
||||
span,
|
||||
&requested_url,
|
||||
args.raw,
|
||||
request_flags,
|
||||
response,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -10,6 +10,8 @@ use crate::network::http::client::{
|
|||
request_handle_response, request_set_timeout, send_request,
|
||||
};
|
||||
|
||||
use super::client::RequestFlags;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SubCommand;
|
||||
|
||||
|
@ -61,6 +63,16 @@ impl Command for SubCommand {
|
|||
"allow insecure server connections when using SSL",
|
||||
Some('k'),
|
||||
)
|
||||
.switch(
|
||||
"full",
|
||||
"returns the full response instead of only the body",
|
||||
Some('f'),
|
||||
)
|
||||
.switch(
|
||||
"allow-errors",
|
||||
"do not fail if the server returns an error code",
|
||||
Some('e'),
|
||||
)
|
||||
.filter()
|
||||
.category(Category::Network)
|
||||
}
|
||||
|
@ -118,6 +130,8 @@ struct Arguments {
|
|||
user: Option<String>,
|
||||
password: Option<String>,
|
||||
timeout: Option<Value>,
|
||||
full: bool,
|
||||
allow_errors: bool,
|
||||
}
|
||||
|
||||
fn run_get(
|
||||
|
@ -134,6 +148,8 @@ fn run_get(
|
|||
user: call.get_flag(engine_state, stack, "user")?,
|
||||
password: call.get_flag(engine_state, stack, "password")?,
|
||||
timeout: call.get_flag(engine_state, stack, "max-time")?,
|
||||
full: call.has_flag("full"),
|
||||
allow_errors: call.has_flag("allow-errors"),
|
||||
};
|
||||
helper(engine_state, stack, call, args)
|
||||
}
|
||||
|
@ -156,13 +172,20 @@ fn helper(
|
|||
request = request_add_authorization_header(args.user, args.password, request);
|
||||
request = request_add_custom_headers(args.headers, request)?;
|
||||
|
||||
let response = send_request(request, span, None, None);
|
||||
let response = send_request(request, None, None);
|
||||
|
||||
let request_flags = RequestFlags {
|
||||
raw: args.raw,
|
||||
full: args.full,
|
||||
allow_errors: args.allow_errors,
|
||||
};
|
||||
|
||||
request_handle_response(
|
||||
engine_state,
|
||||
stack,
|
||||
span,
|
||||
&requested_url,
|
||||
args.raw,
|
||||
request_flags,
|
||||
response,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -143,7 +143,7 @@ fn helper(call: &Call, args: Arguments) -> Result<PipelineData, ShellError> {
|
|||
request = request_add_authorization_header(args.user, args.password, request);
|
||||
request = request_add_custom_headers(args.headers, request)?;
|
||||
|
||||
let response = send_request(request, span, None, None);
|
||||
let response = send_request(request, None, None);
|
||||
request_handle_response_headers(span, response)
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,8 @@ use crate::network::http::client::{
|
|||
request_handle_response, request_set_timeout, send_request,
|
||||
};
|
||||
|
||||
use super::client::RequestFlags;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SubCommand;
|
||||
|
||||
|
@ -64,6 +66,16 @@ impl Command for SubCommand {
|
|||
"allow insecure server connections when using SSL",
|
||||
Some('k'),
|
||||
)
|
||||
.switch(
|
||||
"full",
|
||||
"returns the full response instead of only the body",
|
||||
Some('f'),
|
||||
)
|
||||
.switch(
|
||||
"allow-errors",
|
||||
"do not fail if the server returns an error code",
|
||||
Some('e'),
|
||||
)
|
||||
.filter()
|
||||
.category(Category::Network)
|
||||
}
|
||||
|
@ -126,6 +138,8 @@ struct Arguments {
|
|||
user: Option<String>,
|
||||
password: Option<String>,
|
||||
timeout: Option<Value>,
|
||||
full: bool,
|
||||
allow_errors: bool,
|
||||
}
|
||||
|
||||
fn run_patch(
|
||||
|
@ -144,6 +158,8 @@ fn run_patch(
|
|||
user: call.get_flag(engine_state, stack, "user")?,
|
||||
password: call.get_flag(engine_state, stack, "password")?,
|
||||
timeout: call.get_flag(engine_state, stack, "max-time")?,
|
||||
full: call.has_flag("full"),
|
||||
allow_errors: call.has_flag("allow-errors"),
|
||||
};
|
||||
|
||||
helper(engine_state, stack, call, args)
|
||||
|
@ -167,13 +183,20 @@ fn helper(
|
|||
request = request_add_authorization_header(args.user, args.password, request);
|
||||
request = request_add_custom_headers(args.headers, request)?;
|
||||
|
||||
let response = send_request(request, span, Some(args.data), args.content_type);
|
||||
let response = send_request(request, Some(args.data), args.content_type);
|
||||
|
||||
let request_flags = RequestFlags {
|
||||
raw: args.raw,
|
||||
full: args.full,
|
||||
allow_errors: args.allow_errors,
|
||||
};
|
||||
|
||||
request_handle_response(
|
||||
engine_state,
|
||||
stack,
|
||||
span,
|
||||
&requested_url,
|
||||
args.raw,
|
||||
request_flags,
|
||||
response,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -10,6 +10,8 @@ use crate::network::http::client::{
|
|||
request_handle_response, request_set_timeout, send_request,
|
||||
};
|
||||
|
||||
use super::client::RequestFlags;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SubCommand;
|
||||
|
||||
|
@ -64,6 +66,16 @@ impl Command for SubCommand {
|
|||
"allow insecure server connections when using SSL",
|
||||
Some('k'),
|
||||
)
|
||||
.switch(
|
||||
"full",
|
||||
"returns the full response instead of only the body",
|
||||
Some('f'),
|
||||
)
|
||||
.switch(
|
||||
"allow-errors",
|
||||
"do not fail if the server returns an error code",
|
||||
Some('e'),
|
||||
)
|
||||
.filter()
|
||||
.category(Category::Network)
|
||||
}
|
||||
|
@ -126,6 +138,8 @@ struct Arguments {
|
|||
user: Option<String>,
|
||||
password: Option<String>,
|
||||
timeout: Option<Value>,
|
||||
full: bool,
|
||||
allow_errors: bool,
|
||||
}
|
||||
|
||||
fn run_post(
|
||||
|
@ -144,6 +158,8 @@ fn run_post(
|
|||
user: call.get_flag(engine_state, stack, "user")?,
|
||||
password: call.get_flag(engine_state, stack, "password")?,
|
||||
timeout: call.get_flag(engine_state, stack, "max-time")?,
|
||||
full: call.has_flag("full"),
|
||||
allow_errors: call.has_flag("allow-errors"),
|
||||
};
|
||||
|
||||
helper(engine_state, stack, call, args)
|
||||
|
@ -167,13 +183,20 @@ fn helper(
|
|||
request = request_add_authorization_header(args.user, args.password, request);
|
||||
request = request_add_custom_headers(args.headers, request)?;
|
||||
|
||||
let response = send_request(request, span, Some(args.data), args.content_type);
|
||||
let response = send_request(request, Some(args.data), args.content_type);
|
||||
|
||||
let request_flags = RequestFlags {
|
||||
raw: args.raw,
|
||||
full: args.full,
|
||||
allow_errors: args.allow_errors,
|
||||
};
|
||||
|
||||
request_handle_response(
|
||||
engine_state,
|
||||
stack,
|
||||
span,
|
||||
&requested_url,
|
||||
args.raw,
|
||||
request_flags,
|
||||
response,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -10,6 +10,8 @@ use crate::network::http::client::{
|
|||
request_handle_response, request_set_timeout, send_request,
|
||||
};
|
||||
|
||||
use super::client::RequestFlags;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SubCommand;
|
||||
|
||||
|
@ -64,6 +66,16 @@ impl Command for SubCommand {
|
|||
"allow insecure server connections when using SSL",
|
||||
Some('k'),
|
||||
)
|
||||
.switch(
|
||||
"full",
|
||||
"returns the full response instead of only the body",
|
||||
Some('f'),
|
||||
)
|
||||
.switch(
|
||||
"allow-errors",
|
||||
"do not fail if the server returns an error code",
|
||||
Some('e'),
|
||||
)
|
||||
.filter()
|
||||
.category(Category::Network)
|
||||
}
|
||||
|
@ -126,6 +138,8 @@ struct Arguments {
|
|||
user: Option<String>,
|
||||
password: Option<String>,
|
||||
timeout: Option<Value>,
|
||||
full: bool,
|
||||
allow_errors: bool,
|
||||
}
|
||||
|
||||
fn run_put(
|
||||
|
@ -144,6 +158,8 @@ fn run_put(
|
|||
user: call.get_flag(engine_state, stack, "user")?,
|
||||
password: call.get_flag(engine_state, stack, "password")?,
|
||||
timeout: call.get_flag(engine_state, stack, "max-time")?,
|
||||
full: call.has_flag("full"),
|
||||
allow_errors: call.has_flag("allow-errors"),
|
||||
};
|
||||
|
||||
helper(engine_state, stack, call, args)
|
||||
|
@ -167,13 +183,20 @@ fn helper(
|
|||
request = request_add_authorization_header(args.user, args.password, request);
|
||||
request = request_add_custom_headers(args.headers, request)?;
|
||||
|
||||
let response = send_request(request, span, Some(args.data), args.content_type);
|
||||
let response = send_request(request, Some(args.data), args.content_type);
|
||||
|
||||
let request_flags = RequestFlags {
|
||||
raw: args.raw,
|
||||
full: args.full,
|
||||
allow_errors: args.allow_errors,
|
||||
};
|
||||
|
||||
request_handle_response(
|
||||
engine_state,
|
||||
stack,
|
||||
span,
|
||||
&requested_url,
|
||||
args.raw,
|
||||
request_flags,
|
||||
response,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -39,6 +39,80 @@ fn http_get_failed_due_to_server_error() {
|
|||
assert!(actual.err.contains("Bad request (400)"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn http_get_with_accept_errors() {
|
||||
let mut server = Server::new();
|
||||
|
||||
let _mock = server
|
||||
.mock("GET", "/")
|
||||
.with_status(400)
|
||||
.with_body("error body")
|
||||
.create();
|
||||
|
||||
let actual = nu!(pipeline(
|
||||
format!(
|
||||
r#"
|
||||
http get -e {url}
|
||||
"#,
|
||||
url = server.url()
|
||||
)
|
||||
.as_str()
|
||||
));
|
||||
|
||||
assert!(actual.out.contains("error body"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn http_get_with_accept_errors_and_full_raw_response() {
|
||||
let mut server = Server::new();
|
||||
|
||||
let _mock = server
|
||||
.mock("GET", "/")
|
||||
.with_status(400)
|
||||
.with_body("error body")
|
||||
.create();
|
||||
|
||||
let actual = nu!(pipeline(
|
||||
format!(
|
||||
r#"
|
||||
http get -e -f {url} | $"($in.status) => ($in.body)"
|
||||
"#,
|
||||
url = server.url()
|
||||
)
|
||||
.as_str()
|
||||
));
|
||||
|
||||
assert!(actual.out.contains("400 => error body"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn http_get_with_accept_errors_and_full_json_response() {
|
||||
let mut server = Server::new();
|
||||
|
||||
let _mock = server
|
||||
.mock("GET", "/")
|
||||
.with_status(400)
|
||||
.with_header("content-type", "application/json")
|
||||
.with_body(
|
||||
r#"
|
||||
{"msg": "error body"}
|
||||
"#,
|
||||
)
|
||||
.create();
|
||||
|
||||
let actual = nu!(pipeline(
|
||||
format!(
|
||||
r#"
|
||||
http get -e -f {url} | $"($in.status) => ($in.body.msg)"
|
||||
"#,
|
||||
url = server.url()
|
||||
)
|
||||
.as_str()
|
||||
));
|
||||
|
||||
assert!(actual.out.contains("400 => error body"))
|
||||
}
|
||||
|
||||
// These tests require network access; they use badssl.com which is a Google-affiliated site for testing various SSL errors.
|
||||
// Revisit this if these tests prove to be flaky or unstable.
|
||||
|
||||
|
|
Loading…
Reference in a new issue