mirror of
https://github.com/nushell/nushell.git
synced 2025-02-16 18:41:44 +01: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
@ -132,17 +132,27 @@ pub fn request_add_authorization_header(
|
|||||||
request
|
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(
|
pub fn send_request(
|
||||||
request: Request,
|
request: Request,
|
||||||
span: Span,
|
|
||||||
body: Option<Value>,
|
body: Option<Value>,
|
||||||
content_type: Option<String>,
|
content_type: Option<String>,
|
||||||
) -> Result<Response, ShellError> {
|
) -> Result<Response, ShellErrorOrRequestError> {
|
||||||
let request_url = request.url().to_string();
|
let request_url = request.url().to_string();
|
||||||
|
let error_handler = |err: Error| -> ShellErrorOrRequestError {
|
||||||
|
ShellErrorOrRequestError::RequestError(request_url, err)
|
||||||
|
};
|
||||||
if body.is_none() {
|
if body.is_none() {
|
||||||
return request
|
return request.call().map_err(error_handler);
|
||||||
.call()
|
|
||||||
.map_err(|err| handle_response_error(span, &request_url, err));
|
|
||||||
}
|
}
|
||||||
let body = body.expect("Should never be none.");
|
let body = body.expect("Should never be none.");
|
||||||
|
|
||||||
@ -152,23 +162,18 @@ pub fn send_request(
|
|||||||
_ => BodyType::Unknown,
|
_ => BodyType::Unknown,
|
||||||
};
|
};
|
||||||
match body {
|
match body {
|
||||||
Value::Binary { val, .. } => request
|
Value::Binary { val, .. } => request.send_bytes(&val).map_err(error_handler),
|
||||||
.send_bytes(&val)
|
Value::String { val, .. } => request.send_string(&val).map_err(error_handler),
|
||||||
.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::Record { .. } if body_type == BodyType::Json => {
|
Value::Record { .. } if body_type == BodyType::Json => {
|
||||||
let data = value_to_json_value(&body)?;
|
let data = value_to_json_value(&body);
|
||||||
request
|
request.send_json(data).map_err(error_handler)
|
||||||
.send_json(data)
|
|
||||||
.map_err(|err| handle_response_error(span, &request_url, err))
|
|
||||||
}
|
}
|
||||||
Value::Record { cols, vals, .. } if body_type == BodyType::Form => {
|
Value::Record { cols, vals, .. } if body_type == BodyType::Form => {
|
||||||
let mut data: Vec<(String, String)> = Vec::with_capacity(cols.len());
|
let mut data: Vec<(String, String)> = Vec::with_capacity(cols.len());
|
||||||
|
|
||||||
for (col, val) in cols.iter().zip(vals.iter()) {
|
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
|
let data = data
|
||||||
@ -176,30 +181,35 @@ pub fn send_request(
|
|||||||
.map(|(a, b)| (a.as_str(), b.as_str()))
|
.map(|(a, b)| (a.as_str(), b.as_str()))
|
||||||
.collect::<Vec<(&str, &str)>>();
|
.collect::<Vec<(&str, &str)>>();
|
||||||
|
|
||||||
request
|
request.send_form(&data[..]).map_err(error_handler)
|
||||||
.send_form(&data[..])
|
|
||||||
.map_err(|err| handle_response_error(span, &request_url, err))
|
|
||||||
}
|
}
|
||||||
Value::List { vals, .. } if body_type == BodyType::Form => {
|
Value::List { vals, .. } if body_type == BodyType::Form => {
|
||||||
if vals.len() % 2 != 0 {
|
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
|
let data = vals
|
||||||
.chunks(2)
|
.chunks(2)
|
||||||
.map(|it| Ok((it[0].as_string()?, it[1].as_string()?)))
|
.map(|it| {
|
||||||
.collect::<Result<Vec<(String, String)>, ShellError>>()?;
|
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
|
let data = data
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(a, b)| (a.as_str(), b.as_str()))
|
.map(|(a, b)| (a.as_str(), b.as_str()))
|
||||||
.collect::<Vec<(&str, &str)>>();
|
.collect::<Vec<(&str, &str)>>();
|
||||||
|
|
||||||
request
|
request.send_form(&data).map_err(error_handler)
|
||||||
.send_form(&data)
|
|
||||||
.map_err(|err| handle_response_error(span, &request_url, err))
|
|
||||||
}
|
}
|
||||||
_ => 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(
|
pub fn request_handle_response(
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
span: Span,
|
span: Span,
|
||||||
requested_url: &String,
|
requested_url: &str,
|
||||||
raw: bool,
|
flags: RequestFlags,
|
||||||
response: Result<Response, ShellError>,
|
response: Result<Response, ShellErrorOrRequestError>,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
match response {
|
match response {
|
||||||
Ok(resp) => match resp.header("content-type") {
|
Ok(resp) => {
|
||||||
Some(content_type) => {
|
request_handle_response_content(engine_state, stack, span, requested_url, flags, resp)
|
||||||
let content_type = mime::Mime::from_str(content_type).map_err(|_| {
|
}
|
||||||
ShellError::GenericError(
|
Err(e) => match e {
|
||||||
format!("MIME type unknown: {content_type}"),
|
ShellErrorOrRequestError::ShellError(e) => Err(e),
|
||||||
"".to_string(),
|
ShellErrorOrRequestError::RequestError(_, e) => {
|
||||||
None,
|
if flags.allow_errors {
|
||||||
Some("given unknown MIME type".to_string()),
|
if let Error::Status(_, resp) = e {
|
||||||
Vec::new(),
|
Ok(request_handle_response_content(
|
||||||
)
|
|
||||||
})?;
|
|
||||||
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(
|
|
||||||
engine_state,
|
engine_state,
|
||||||
stack,
|
stack,
|
||||||
&Call::new(span),
|
span,
|
||||||
output,
|
requested_url,
|
||||||
),
|
flags,
|
||||||
None => Ok(output),
|
resp,
|
||||||
|
)?)
|
||||||
|
} else {
|
||||||
|
Err(handle_response_error(span, requested_url, e))
|
||||||
}
|
}
|
||||||
} else {
|
} 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(
|
pub fn request_handle_response_headers(
|
||||||
span: Span,
|
span: Span,
|
||||||
response: Result<Response, ShellError>,
|
response: Result<Response, ShellErrorOrRequestError>,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
match response {
|
match response {
|
||||||
Ok(resp) => {
|
Ok(resp) => request_handle_response_headers_raw(span, &resp),
|
||||||
let cols = resp.headers_names();
|
Err(e) => match e {
|
||||||
|
ShellErrorOrRequestError::ShellError(e) => Err(e),
|
||||||
let mut vals = Vec::with_capacity(cols.len());
|
ShellErrorOrRequestError::RequestError(requested_url, e) => {
|
||||||
for key in &cols {
|
Err(handle_response_error(span, &requested_url, e))
|
||||||
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(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,
|
request_handle_response, request_set_timeout, send_request,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use super::client::RequestFlags;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SubCommand;
|
pub struct SubCommand;
|
||||||
|
|
||||||
@ -68,6 +70,16 @@ impl Command for SubCommand {
|
|||||||
"allow insecure server connections when using SSL",
|
"allow insecure server connections when using SSL",
|
||||||
Some('k'),
|
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()
|
.filter()
|
||||||
.category(Category::Network)
|
.category(Category::Network)
|
||||||
}
|
}
|
||||||
@ -136,6 +148,8 @@ struct Arguments {
|
|||||||
user: Option<String>,
|
user: Option<String>,
|
||||||
password: Option<String>,
|
password: Option<String>,
|
||||||
timeout: Option<Value>,
|
timeout: Option<Value>,
|
||||||
|
full: bool,
|
||||||
|
allow_errors: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_delete(
|
fn run_delete(
|
||||||
@ -154,6 +168,8 @@ fn run_delete(
|
|||||||
user: call.get_flag(engine_state, stack, "user")?,
|
user: call.get_flag(engine_state, stack, "user")?,
|
||||||
password: call.get_flag(engine_state, stack, "password")?,
|
password: call.get_flag(engine_state, stack, "password")?,
|
||||||
timeout: call.get_flag(engine_state, stack, "max-time")?,
|
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)
|
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_authorization_header(args.user, args.password, request);
|
||||||
request = request_add_custom_headers(args.headers, 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(
|
request_handle_response(
|
||||||
engine_state,
|
engine_state,
|
||||||
stack,
|
stack,
|
||||||
span,
|
span,
|
||||||
&requested_url,
|
&requested_url,
|
||||||
args.raw,
|
request_flags,
|
||||||
response,
|
response,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,8 @@ use crate::network::http::client::{
|
|||||||
request_handle_response, request_set_timeout, send_request,
|
request_handle_response, request_set_timeout, send_request,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use super::client::RequestFlags;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SubCommand;
|
pub struct SubCommand;
|
||||||
|
|
||||||
@ -61,6 +63,16 @@ impl Command for SubCommand {
|
|||||||
"allow insecure server connections when using SSL",
|
"allow insecure server connections when using SSL",
|
||||||
Some('k'),
|
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()
|
.filter()
|
||||||
.category(Category::Network)
|
.category(Category::Network)
|
||||||
}
|
}
|
||||||
@ -118,6 +130,8 @@ struct Arguments {
|
|||||||
user: Option<String>,
|
user: Option<String>,
|
||||||
password: Option<String>,
|
password: Option<String>,
|
||||||
timeout: Option<Value>,
|
timeout: Option<Value>,
|
||||||
|
full: bool,
|
||||||
|
allow_errors: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_get(
|
fn run_get(
|
||||||
@ -134,6 +148,8 @@ fn run_get(
|
|||||||
user: call.get_flag(engine_state, stack, "user")?,
|
user: call.get_flag(engine_state, stack, "user")?,
|
||||||
password: call.get_flag(engine_state, stack, "password")?,
|
password: call.get_flag(engine_state, stack, "password")?,
|
||||||
timeout: call.get_flag(engine_state, stack, "max-time")?,
|
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)
|
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_authorization_header(args.user, args.password, request);
|
||||||
request = request_add_custom_headers(args.headers, 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(
|
request_handle_response(
|
||||||
engine_state,
|
engine_state,
|
||||||
stack,
|
stack,
|
||||||
span,
|
span,
|
||||||
&requested_url,
|
&requested_url,
|
||||||
args.raw,
|
request_flags,
|
||||||
response,
|
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_authorization_header(args.user, args.password, request);
|
||||||
request = request_add_custom_headers(args.headers, 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)
|
request_handle_response_headers(span, response)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,6 +10,8 @@ use crate::network::http::client::{
|
|||||||
request_handle_response, request_set_timeout, send_request,
|
request_handle_response, request_set_timeout, send_request,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use super::client::RequestFlags;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SubCommand;
|
pub struct SubCommand;
|
||||||
|
|
||||||
@ -64,6 +66,16 @@ impl Command for SubCommand {
|
|||||||
"allow insecure server connections when using SSL",
|
"allow insecure server connections when using SSL",
|
||||||
Some('k'),
|
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()
|
.filter()
|
||||||
.category(Category::Network)
|
.category(Category::Network)
|
||||||
}
|
}
|
||||||
@ -126,6 +138,8 @@ struct Arguments {
|
|||||||
user: Option<String>,
|
user: Option<String>,
|
||||||
password: Option<String>,
|
password: Option<String>,
|
||||||
timeout: Option<Value>,
|
timeout: Option<Value>,
|
||||||
|
full: bool,
|
||||||
|
allow_errors: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_patch(
|
fn run_patch(
|
||||||
@ -144,6 +158,8 @@ fn run_patch(
|
|||||||
user: call.get_flag(engine_state, stack, "user")?,
|
user: call.get_flag(engine_state, stack, "user")?,
|
||||||
password: call.get_flag(engine_state, stack, "password")?,
|
password: call.get_flag(engine_state, stack, "password")?,
|
||||||
timeout: call.get_flag(engine_state, stack, "max-time")?,
|
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)
|
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_authorization_header(args.user, args.password, request);
|
||||||
request = request_add_custom_headers(args.headers, 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(
|
request_handle_response(
|
||||||
engine_state,
|
engine_state,
|
||||||
stack,
|
stack,
|
||||||
span,
|
span,
|
||||||
&requested_url,
|
&requested_url,
|
||||||
args.raw,
|
request_flags,
|
||||||
response,
|
response,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,8 @@ use crate::network::http::client::{
|
|||||||
request_handle_response, request_set_timeout, send_request,
|
request_handle_response, request_set_timeout, send_request,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use super::client::RequestFlags;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SubCommand;
|
pub struct SubCommand;
|
||||||
|
|
||||||
@ -64,6 +66,16 @@ impl Command for SubCommand {
|
|||||||
"allow insecure server connections when using SSL",
|
"allow insecure server connections when using SSL",
|
||||||
Some('k'),
|
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()
|
.filter()
|
||||||
.category(Category::Network)
|
.category(Category::Network)
|
||||||
}
|
}
|
||||||
@ -126,6 +138,8 @@ struct Arguments {
|
|||||||
user: Option<String>,
|
user: Option<String>,
|
||||||
password: Option<String>,
|
password: Option<String>,
|
||||||
timeout: Option<Value>,
|
timeout: Option<Value>,
|
||||||
|
full: bool,
|
||||||
|
allow_errors: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_post(
|
fn run_post(
|
||||||
@ -144,6 +158,8 @@ fn run_post(
|
|||||||
user: call.get_flag(engine_state, stack, "user")?,
|
user: call.get_flag(engine_state, stack, "user")?,
|
||||||
password: call.get_flag(engine_state, stack, "password")?,
|
password: call.get_flag(engine_state, stack, "password")?,
|
||||||
timeout: call.get_flag(engine_state, stack, "max-time")?,
|
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)
|
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_authorization_header(args.user, args.password, request);
|
||||||
request = request_add_custom_headers(args.headers, 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(
|
request_handle_response(
|
||||||
engine_state,
|
engine_state,
|
||||||
stack,
|
stack,
|
||||||
span,
|
span,
|
||||||
&requested_url,
|
&requested_url,
|
||||||
args.raw,
|
request_flags,
|
||||||
response,
|
response,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,8 @@ use crate::network::http::client::{
|
|||||||
request_handle_response, request_set_timeout, send_request,
|
request_handle_response, request_set_timeout, send_request,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use super::client::RequestFlags;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SubCommand;
|
pub struct SubCommand;
|
||||||
|
|
||||||
@ -64,6 +66,16 @@ impl Command for SubCommand {
|
|||||||
"allow insecure server connections when using SSL",
|
"allow insecure server connections when using SSL",
|
||||||
Some('k'),
|
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()
|
.filter()
|
||||||
.category(Category::Network)
|
.category(Category::Network)
|
||||||
}
|
}
|
||||||
@ -126,6 +138,8 @@ struct Arguments {
|
|||||||
user: Option<String>,
|
user: Option<String>,
|
||||||
password: Option<String>,
|
password: Option<String>,
|
||||||
timeout: Option<Value>,
|
timeout: Option<Value>,
|
||||||
|
full: bool,
|
||||||
|
allow_errors: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_put(
|
fn run_put(
|
||||||
@ -144,6 +158,8 @@ fn run_put(
|
|||||||
user: call.get_flag(engine_state, stack, "user")?,
|
user: call.get_flag(engine_state, stack, "user")?,
|
||||||
password: call.get_flag(engine_state, stack, "password")?,
|
password: call.get_flag(engine_state, stack, "password")?,
|
||||||
timeout: call.get_flag(engine_state, stack, "max-time")?,
|
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)
|
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_authorization_header(args.user, args.password, request);
|
||||||
request = request_add_custom_headers(args.headers, 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(
|
request_handle_response(
|
||||||
engine_state,
|
engine_state,
|
||||||
stack,
|
stack,
|
||||||
span,
|
span,
|
||||||
&requested_url,
|
&requested_url,
|
||||||
args.raw,
|
request_flags,
|
||||||
response,
|
response,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -39,6 +39,80 @@ fn http_get_failed_due_to_server_error() {
|
|||||||
assert!(actual.err.contains("Bad request (400)"))
|
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.
|
// 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.
|
// Revisit this if these tests prove to be flaky or unstable.
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user