"Use pipeline data in http post command. Added metadata with content_type"

This commit is contained in:
Jack Wright 2024-06-27 16:59:54 -07:00
parent 0d79b63711
commit c9eb9f20c3
5 changed files with 161 additions and 79 deletions

View File

@ -90,6 +90,9 @@ impl Command for Metadata {
"source",
Value::string(path.to_string_lossy().to_string(), head),
),
PipelineMetadata {
data_source: DataSource::ContentType(content_type),
} => record.push("content_type", Value::string(content_type, head)),
}
}
@ -143,6 +146,9 @@ fn build_metadata_record(arg: &Value, metadata: Option<&PipelineMetadata>, head:
"source",
Value::string(path.to_string_lossy().to_string(), head),
),
PipelineMetadata {
data_source: DataSource::ContentType(content_type),
} => record.push("content_type", Value::string(content_type, head)),
}
}

View File

@ -1,5 +1,5 @@
use nu_engine::command_prelude::*;
use nu_protocol::ast::PathMember;
use nu_protocol::{ast::PathMember, PipelineMetadata};
#[derive(Clone)]
pub struct ToJson;
@ -61,7 +61,11 @@ impl Command for ToJson {
match json_result {
Ok(serde_json_string) => {
Ok(Value::string(serde_json_string, span).into_pipeline_data())
let res = Value::string(serde_json_string, span);
let metadata = PipelineMetadata {
data_source: nu_protocol::DataSource::ContentType("application/json".to_string())
};
Ok(PipelineData::Value(res, Some(metadata)))
}
_ => Ok(Value::error(
ShellError::CantConvert {

View File

@ -180,88 +180,135 @@ impl From<ShellError> for ShellErrorOrRequestError {
}
}
#[derive(Debug)]
pub enum HttpBody {
Value(Value),
ByteStream(ByteStream),
None,
}
pub fn send_request(
request: Request,
body: Option<Value>,
content_type: Option<String>,
ctrl_c: Option<Arc<AtomicBool>>,
) -> Result<Response, ShellErrorOrRequestError> {
let request_url = request.url().to_string();
if body.is_none() {
return send_cancellable_request(&request_url, Box::new(|| request.call()), ctrl_c);
}
let body = body.expect("Should never be none.");
let body_type = match content_type {
Some(it) if it == "application/json" => BodyType::Json,
Some(it) if it == "application/x-www-form-urlencoded" => BodyType::Form,
_ => BodyType::Unknown,
let body = if let Some(body) = body {
HttpBody::Value(body)
} else {
HttpBody::None
};
match body {
Value::Binary { val, .. } => send_cancellable_request(
&request_url,
Box::new(move || request.send_bytes(&val)),
ctrl_c,
),
Value::String { .. } if body_type == BodyType::Json => {
let data = value_to_json_value(&body)?;
send_cancellable_request(&request_url, Box::new(|| request.send_json(data)), ctrl_c)
}
Value::String { val, .. } => send_cancellable_request(
&request_url,
Box::new(move || request.send_string(&val)),
ctrl_c,
),
Value::Record { .. } if body_type == BodyType::Json => {
let data = value_to_json_value(&body)?;
send_cancellable_request(&request_url, Box::new(|| request.send_json(data)), ctrl_c)
}
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()?))
}
send_request2(request, body, content_type, ctrl_c)
}
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)>>();
request.send_form(&data)
// remove once all commands have been migrated
pub fn send_request2(
request: Request,
http_body: HttpBody,
content_type: Option<String>,
ctrl_c: Option<Arc<AtomicBool>>,
) -> Result<Response, ShellErrorOrRequestError> {
let request_url = request.url().to_string();
match http_body {
HttpBody::None => {
return send_cancellable_request(&request_url, Box::new(|| request.call()), ctrl_c);
}
HttpBody::ByteStream(byte_stream) => {
let bytes = byte_stream.into_bytes()?;
send_cancellable_request(
&request_url,
Box::new(move || request.send_bytes(&bytes)),
ctrl_c,
)
}
HttpBody::Value(body) => {
let body_type = match content_type {
Some(it) if it == "application/json" => BodyType::Json,
Some(it) if it == "application/x-www-form-urlencoded" => BodyType::Form,
_ => BodyType::Unknown,
};
send_cancellable_request(&request_url, Box::new(request_fn), ctrl_c)
}
Value::List { vals, .. } if body_type == BodyType::Form => {
if vals.len() % 2 != 0 {
return Err(ShellErrorOrRequestError::ShellError(ShellError::IOError {
match body {
Value::Binary { val, .. } => send_cancellable_request(
&request_url,
Box::new(move || request.send_bytes(&val)),
ctrl_c,
),
Value::String { .. } if body_type == BodyType::Json => {
let data = value_to_json_value(&body)?;
send_cancellable_request(
&request_url,
Box::new(|| request.send_json(data)),
ctrl_c,
)
}
Value::String { val, .. } => send_cancellable_request(
&request_url,
Box::new(move || request.send_string(&val)),
ctrl_c,
),
Value::Record { .. } if body_type == BodyType::Json => {
let data = value_to_json_value(&body)?;
send_cancellable_request(
&request_url,
Box::new(|| request.send_json(data)),
ctrl_c,
)
}
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 || {
// 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)>>();
request.send_form(&data)
};
send_cancellable_request(&request_url, Box::new(request_fn), ctrl_c)
}
Value::List { vals, .. } if body_type == BodyType::Form => {
if vals.len() % 2 != 0 {
return Err(ShellErrorOrRequestError::ShellError(ShellError::IOError {
msg: "unsupported body input".into(),
}));
}
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)>>();
request.send_form(&data)
};
send_cancellable_request(&request_url, Box::new(request_fn), ctrl_c)
}
Value::List { .. } if body_type == BodyType::Json => {
let data = value_to_json_value(&body)?;
send_cancellable_request(
&request_url,
Box::new(|| request.send_json(data)),
ctrl_c,
)
}
_ => Err(ShellErrorOrRequestError::ShellError(ShellError::IOError {
msg: "unsupported body input".into(),
}));
})),
}
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)>>();
request.send_form(&data)
};
send_cancellable_request(&request_url, Box::new(request_fn), ctrl_c)
}
Value::List { .. } if body_type == BodyType::Json => {
let data = value_to_json_value(&body)?;
send_cancellable_request(&request_url, Box::new(|| request.send_json(data)), ctrl_c)
}
_ => Err(ShellErrorOrRequestError::ShellError(ShellError::IOError {
msg: "unsupported body input".into(),
})),
}
}

View File

@ -1,9 +1,12 @@
use crate::network::http::client::{
check_response_redirection, http_client, http_parse_redirect_mode, http_parse_url,
request_add_authorization_header, request_add_custom_headers, request_handle_response,
request_set_timeout, send_request, RequestFlags,
request_set_timeout, RequestFlags, HttpBody
};
use nu_engine::command_prelude::*;
use nu_protocol::DataSource;
use super::client::send_request2;
#[derive(Clone)]
pub struct SubCommand;
@ -15,10 +18,10 @@ impl Command for SubCommand {
fn signature(&self) -> Signature {
Signature::build("http post")
.input_output_types(vec![(Type::Nothing, Type::Any)])
.input_output_types(vec![(Type::Any, Type::Any)])
.allow_variants_without_examples(true)
.required("URL", SyntaxShape::String, "The URL to post to.")
.required("data", SyntaxShape::Any, "The contents of the post body.")
.optional("data", SyntaxShape::Any, "The contents of the post body. Required unless part of pipeline.")
.named(
"user",
SyntaxShape::Any,
@ -129,7 +132,7 @@ impl Command for SubCommand {
struct Arguments {
url: Value,
headers: Option<Value>,
data: Value,
data: HttpBody,
content_type: Option<String>,
raw: bool,
insecure: bool,
@ -145,13 +148,34 @@ fn run_post(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
_input: PipelineData,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let (data, maybe_metadata) = call.opt::<Value>(engine_state, stack, 1)?
.map(|v| (HttpBody::Value(v), None))
.unwrap_or_else(|| match input {
PipelineData::Value(v, metadata) => (HttpBody::Value(v), metadata),
PipelineData::ByteStream(byte_stream, metadata) => (HttpBody::ByteStream(byte_stream), metadata),
_ => (HttpBody::None, None)
});
let content_type = call.get_flag(engine_state, stack, "content-type")?
.or_else(|| {
if let Some(metadata) = maybe_metadata {
match metadata.data_source {
DataSource::ContentType(content_type) => Some(content_type),
_ => None
}
} else {
None
}
});
let args = Arguments {
url: call.req(engine_state, stack, 0)?,
headers: call.get_flag(engine_state, stack, "headers")?,
data: call.req(engine_state, stack, 1)?,
content_type: call.get_flag(engine_state, stack, "content-type")?,
data,
content_type,
raw: call.has_flag(engine_state, stack, "raw")?,
insecure: call.has_flag(engine_state, stack, "insecure")?,
user: call.get_flag(engine_state, stack, "user")?,
@ -185,7 +209,7 @@ 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.clone(), Some(args.data), args.content_type, ctrl_c);
let response = send_request2(request.clone(), args.data, args.content_type, ctrl_c);
let request_flags = RequestFlags {
raw: args.raw,

View File

@ -15,4 +15,5 @@ pub enum DataSource {
Ls,
HtmlThemes,
FilePath(PathBuf),
ContentType(String)
}