mirror of
https://github.com/nushell/nushell.git
synced 2025-06-01 15:46:04 +02:00
Refactor send_request in client.rs (#13701)
Closes #13687 Closes #13686 # Description Light refactoring of `send_request `in `client.rs`. In the end there are more lines but now the logic is more concise and facilitates adding new conditions in the future. Unit tests ran fine and I tested a few cases manually. Cool project btw, I'll be using nushell from now on.
This commit is contained in:
parent
63b94dbd28
commit
4792328d0e
@ -18,6 +18,8 @@ use std::{
|
|||||||
use ureq::{Error, ErrorKind, Request, Response};
|
use ureq::{Error, ErrorKind, Request, Response};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
|
const HTTP_DOCS: &str = "https://www.nushell.sh/cookbook/http.html";
|
||||||
|
|
||||||
#[derive(PartialEq, Eq)]
|
#[derive(PartialEq, Eq)]
|
||||||
pub enum BodyType {
|
pub enum BodyType {
|
||||||
Json,
|
Json,
|
||||||
@ -221,45 +223,48 @@ pub fn send_request(
|
|||||||
_ => (BodyType::Unknown, request),
|
_ => (BodyType::Unknown, request),
|
||||||
};
|
};
|
||||||
|
|
||||||
match body {
|
match body_type {
|
||||||
Value::Binary { val, .. } => send_cancellable_request(
|
BodyType::Json => send_json_request(&request_url, body, req, span, signals),
|
||||||
&request_url,
|
BodyType::Form => send_form_request(&request_url, body, req, span, signals),
|
||||||
Box::new(move || req.send_bytes(&val)),
|
BodyType::Multipart => {
|
||||||
span,
|
send_multipart_request(&request_url, body, req, span, signals)
|
||||||
signals,
|
}
|
||||||
),
|
BodyType::Unknown => send_default_request(&request_url, body, req, span, signals),
|
||||||
Value::String { .. } if body_type == BodyType::Json => {
|
}
|
||||||
let data = value_to_json_value(&body)?;
|
|
||||||
send_cancellable_request(
|
|
||||||
&request_url,
|
|
||||||
Box::new(|| req.send_json(data)),
|
|
||||||
span,
|
|
||||||
signals,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
Value::String { val, .. } => send_cancellable_request(
|
|
||||||
&request_url,
|
|
||||||
Box::new(move || req.send_string(&val)),
|
|
||||||
span,
|
|
||||||
signals,
|
|
||||||
),
|
|
||||||
Value::Record { .. } if body_type == BodyType::Json => {
|
|
||||||
let data = value_to_json_value(&body)?;
|
|
||||||
send_cancellable_request(
|
|
||||||
&request_url,
|
|
||||||
Box::new(|| req.send_json(data)),
|
|
||||||
span,
|
|
||||||
signals,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
Value::Record { val, .. } if body_type == BodyType::Form => {
|
|
||||||
let mut data: Vec<(String, String)> = Vec::with_capacity(val.len());
|
|
||||||
|
|
||||||
for (col, val) in val.into_owned() {
|
|
||||||
data.push((col, val.coerce_into_string()?))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let request_fn = move || {
|
fn send_json_request(
|
||||||
|
request_url: &str,
|
||||||
|
body: Value,
|
||||||
|
req: Request,
|
||||||
|
span: Span,
|
||||||
|
signals: &Signals,
|
||||||
|
) -> Result<Response, ShellErrorOrRequestError> {
|
||||||
|
let data = match body {
|
||||||
|
Value::Int { .. } | Value::List { .. } | Value::String { .. } | Value::Record { .. } => {
|
||||||
|
value_to_json_value(&body)?
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Err(ShellErrorOrRequestError::ShellError(
|
||||||
|
ShellError::UnsupportedHttpBody {
|
||||||
|
msg: format!("Accepted types: [Int, List, String, Record]. Check: {HTTP_DOCS}"),
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
send_cancellable_request(request_url, Box::new(|| req.send_json(data)), span, signals)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_form_request(
|
||||||
|
request_url: &str,
|
||||||
|
body: Value,
|
||||||
|
req: Request,
|
||||||
|
span: Span,
|
||||||
|
signals: &Signals,
|
||||||
|
) -> Result<Response, ShellErrorOrRequestError> {
|
||||||
|
let build_request_fn = |data: Vec<(String, String)>| {
|
||||||
// coerce `data` into a shape that send_form() is happy with
|
// coerce `data` into a shape that send_form() is happy with
|
||||||
let data = data
|
let data = data
|
||||||
.iter()
|
.iter()
|
||||||
@ -267,10 +272,50 @@ pub fn send_request(
|
|||||||
.collect::<Vec<(&str, &str)>>();
|
.collect::<Vec<(&str, &str)>>();
|
||||||
req.send_form(&data)
|
req.send_form(&data)
|
||||||
};
|
};
|
||||||
send_cancellable_request(&request_url, Box::new(request_fn), span, signals)
|
|
||||||
|
match body {
|
||||||
|
Value::List { vals, .. } => {
|
||||||
|
if vals.len() % 2 != 0 {
|
||||||
|
return Err(ShellErrorOrRequestError::ShellError(ShellError::UnsupportedHttpBody {
|
||||||
|
msg: "Body type 'List' for form requests requires paired values. E.g.: [value, 10]".into(),
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
// multipart form upload
|
|
||||||
Value::Record { val, .. } if body_type == BodyType::Multipart => {
|
let data = vals
|
||||||
|
.chunks(2)
|
||||||
|
.map(|it| Ok((it[0].coerce_string()?, it[1].coerce_string()?)))
|
||||||
|
.collect::<Result<Vec<(String, String)>, ShellErrorOrRequestError>>()?;
|
||||||
|
|
||||||
|
let request_fn = Box::new(|| build_request_fn(data));
|
||||||
|
send_cancellable_request(request_url, request_fn, span, signals)
|
||||||
|
}
|
||||||
|
Value::Record { val, .. } => {
|
||||||
|
let mut data: Vec<(String, String)> = Vec::with_capacity(val.len());
|
||||||
|
|
||||||
|
for (col, val) in val.into_owned() {
|
||||||
|
data.push((col, val.coerce_into_string()?))
|
||||||
|
}
|
||||||
|
|
||||||
|
let request_fn = Box::new(|| build_request_fn(data));
|
||||||
|
send_cancellable_request(request_url, request_fn, span, signals)
|
||||||
|
}
|
||||||
|
_ => Err(ShellErrorOrRequestError::ShellError(
|
||||||
|
ShellError::UnsupportedHttpBody {
|
||||||
|
msg: format!("Accepted types: [List, Record]. Check: {HTTP_DOCS}"),
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_multipart_request(
|
||||||
|
request_url: &str,
|
||||||
|
body: Value,
|
||||||
|
req: Request,
|
||||||
|
span: Span,
|
||||||
|
signals: &Signals,
|
||||||
|
) -> Result<Response, ShellErrorOrRequestError> {
|
||||||
|
let request_fn = match body {
|
||||||
|
Value::Record { val, .. } => {
|
||||||
let mut builder = MultipartWriter::new();
|
let mut builder = MultipartWriter::new();
|
||||||
|
|
||||||
let err = |e| {
|
let err = |e| {
|
||||||
@ -294,8 +339,7 @@ pub fn send_request(
|
|||||||
.add(&mut Cursor::new(val), &headers.join("\n"))
|
.add(&mut Cursor::new(val), &headers.join("\n"))
|
||||||
.map_err(err)?;
|
.map_err(err)?;
|
||||||
} else {
|
} else {
|
||||||
let headers =
|
let headers = format!(r#"Content-Disposition: form-data; name="{}""#, col);
|
||||||
format!(r#"Content-Disposition: form-data; name="{}""#, col);
|
|
||||||
builder
|
builder
|
||||||
.add(val.coerce_into_string()?.as_bytes(), &headers)
|
.add(val.coerce_into_string()?.as_bytes(), &headers)
|
||||||
.map_err(err)?;
|
.map_err(err)?;
|
||||||
@ -306,47 +350,44 @@ pub fn send_request(
|
|||||||
let (boundary, data) = (builder.boundary, builder.data);
|
let (boundary, data) = (builder.boundary, builder.data);
|
||||||
let content_type = format!("multipart/form-data; boundary={}", boundary);
|
let content_type = format!("multipart/form-data; boundary={}", boundary);
|
||||||
|
|
||||||
let request_fn =
|
move || req.set("Content-Type", &content_type).send_bytes(&data)
|
||||||
move || req.set("Content-Type", &content_type).send_bytes(&data);
|
|
||||||
|
|
||||||
send_cancellable_request(&request_url, Box::new(request_fn), span, signals)
|
|
||||||
}
|
}
|
||||||
Value::List { vals, .. } if body_type == BodyType::Form => {
|
_ => {
|
||||||
if vals.len() % 2 != 0 {
|
return Err(ShellErrorOrRequestError::ShellError(
|
||||||
return Err(ShellErrorOrRequestError::ShellError(ShellError::IOError {
|
ShellError::UnsupportedHttpBody {
|
||||||
msg: "unsupported body input".into(),
|
msg: format!("Accepted types: [Record]. Check: {HTTP_DOCS}"),
|
||||||
}));
|
},
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
let data = vals
|
|
||||||
.chunks(2)
|
|
||||||
.map(|it| Ok((it[0].coerce_string()?, it[1].coerce_string()?)))
|
|
||||||
.collect::<Result<Vec<(String, String)>, ShellErrorOrRequestError>>()?;
|
|
||||||
|
|
||||||
let request_fn = move || {
|
|
||||||
// coerce `data` into a shape that send_form() is happy with
|
|
||||||
let data = data
|
|
||||||
.iter()
|
|
||||||
.map(|(a, b)| (a.as_str(), b.as_str()))
|
|
||||||
.collect::<Vec<(&str, &str)>>();
|
|
||||||
req.send_form(&data)
|
|
||||||
};
|
};
|
||||||
send_cancellable_request(&request_url, Box::new(request_fn), span, signals)
|
send_cancellable_request(request_url, Box::new(request_fn), span, signals)
|
||||||
}
|
}
|
||||||
Value::List { .. } if body_type == BodyType::Json => {
|
|
||||||
let data = value_to_json_value(&body)?;
|
fn send_default_request(
|
||||||
send_cancellable_request(
|
request_url: &str,
|
||||||
&request_url,
|
body: Value,
|
||||||
Box::new(|| req.send_json(data)),
|
req: Request,
|
||||||
|
span: Span,
|
||||||
|
signals: &Signals,
|
||||||
|
) -> Result<Response, ShellErrorOrRequestError> {
|
||||||
|
match body {
|
||||||
|
Value::Binary { val, .. } => send_cancellable_request(
|
||||||
|
request_url,
|
||||||
|
Box::new(move || req.send_bytes(&val)),
|
||||||
span,
|
span,
|
||||||
signals,
|
signals,
|
||||||
)
|
),
|
||||||
}
|
Value::String { val, .. } => send_cancellable_request(
|
||||||
_ => Err(ShellErrorOrRequestError::ShellError(ShellError::IOError {
|
request_url,
|
||||||
msg: "unsupported body input".into(),
|
Box::new(move || req.send_string(&val)),
|
||||||
})),
|
span,
|
||||||
}
|
signals,
|
||||||
}
|
),
|
||||||
|
_ => Err(ShellErrorOrRequestError::ShellError(
|
||||||
|
ShellError::UnsupportedHttpBody {
|
||||||
|
msg: format!("Accepted types: [Binary, String]. Check: {HTTP_DOCS}"),
|
||||||
|
},
|
||||||
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -133,6 +133,36 @@ fn http_post_json_list_is_success() {
|
|||||||
assert!(actual.out.is_empty())
|
assert!(actual.out.is_empty())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn http_post_json_int_is_success() {
|
||||||
|
let mut server = Server::new();
|
||||||
|
|
||||||
|
let mock = server.mock("POST", "/").match_body(r#"50"#).create();
|
||||||
|
|
||||||
|
let actual = nu!(format!(
|
||||||
|
r#"http post -t 'application/json' {url} 50"#,
|
||||||
|
url = server.url()
|
||||||
|
));
|
||||||
|
|
||||||
|
mock.assert();
|
||||||
|
assert!(actual.out.is_empty())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn http_post_json_string_is_success() {
|
||||||
|
let mut server = Server::new();
|
||||||
|
|
||||||
|
let mock = server.mock("POST", "/").match_body(r#""test""#).create();
|
||||||
|
|
||||||
|
let actual = nu!(format!(
|
||||||
|
r#"http post -t 'application/json' {url} "test""#,
|
||||||
|
url = server.url()
|
||||||
|
));
|
||||||
|
|
||||||
|
mock.assert();
|
||||||
|
assert!(actual.out.is_empty())
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn http_post_follows_redirect() {
|
fn http_post_follows_redirect() {
|
||||||
let mut server = Server::new();
|
let mut server = Server::new();
|
||||||
|
@ -639,6 +639,15 @@ pub enum ShellError {
|
|||||||
span: Span,
|
span: Span,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/// An unsupported body input was used for the respective application body type in 'http' command
|
||||||
|
///
|
||||||
|
/// ## Resolution
|
||||||
|
///
|
||||||
|
/// This error is fairly generic. Refer to the specific error message for further details.
|
||||||
|
#[error("Unsupported body for current content type")]
|
||||||
|
#[diagnostic(code(nu::shell::unsupported_body), help("{msg}"))]
|
||||||
|
UnsupportedHttpBody { msg: String },
|
||||||
|
|
||||||
/// An operation was attempted with an input unsupported for some reason.
|
/// An operation was attempted with an input unsupported for some reason.
|
||||||
///
|
///
|
||||||
/// ## Resolution
|
/// ## Resolution
|
||||||
|
Loading…
x
Reference in New Issue
Block a user