1use nu_protocol::Value;
2use std::collections::HashMap;
3use std::path::PathBuf;
4
5#[derive(Clone, Debug, serde::Serialize)]
6#[serde(untagged)]
7pub enum HeaderValue {
8 Single(String),
9 Multiple(Vec<String>),
10}
11
12#[derive(Clone, Debug, Default)]
14pub struct HttpResponseMeta {
15 pub status: Option<u16>,
16 pub headers: HashMap<String, HeaderValue>,
17}
18
19#[derive(Clone, Debug)]
21pub struct Response {
22 pub status: u16,
23 pub headers: HashMap<String, HeaderValue>,
24 pub body_type: ResponseBodyType,
25}
26
27#[derive(Clone, Debug)]
28pub enum ResponseBodyType {
29 Normal,
30 Static {
31 root: PathBuf,
32 path: String,
33 fallback: Option<String>,
34 },
35 ReverseProxy {
36 target_url: String,
37 headers: HashMap<String, HeaderValue>,
38 preserve_host: bool,
39 strip_prefix: Option<String>,
40 request_body: Vec<u8>,
41 query: Option<HashMap<String, String>>,
42 },
43}
44
45#[derive(Debug)]
46pub enum ResponseTransport {
47 Empty,
48 Full(Vec<u8>),
49 Stream(tokio::sync::mpsc::Receiver<Vec<u8>>),
50}
51
52pub fn value_to_json(value: &Value) -> serde_json::Value {
53 match value {
54 Value::Nothing { .. } => serde_json::Value::Null,
55 Value::Bool { val, .. } => serde_json::Value::Bool(*val),
56 Value::Int { val, .. } => serde_json::Value::Number((*val).into()),
57 Value::Float { val, .. } => serde_json::Number::from_f64(*val)
58 .map(serde_json::Value::Number)
59 .unwrap_or(serde_json::Value::Null),
60 Value::String { val, .. } => serde_json::Value::String(val.clone()),
61 Value::List { vals, .. } => {
62 serde_json::Value::Array(vals.iter().map(value_to_json).collect())
63 }
64 Value::Record { val, .. } => {
65 let mut map = serde_json::Map::new();
66 for (k, v) in val.iter() {
67 map.insert(k.clone(), value_to_json(v));
68 }
69 serde_json::Value::Object(map)
70 }
71 other => serde_json::Value::String(
75 other.to_expanded_string(", ", &nu_protocol::Config::default()),
76 ),
77 }
78}
79
80pub fn extract_http_response_meta(
82 metadata: Option<&nu_protocol::PipelineMetadata>,
83) -> HttpResponseMeta {
84 let Some(meta) = metadata else {
85 return HttpResponseMeta::default();
86 };
87
88 let Some(http_response) = meta.custom.get("http.response") else {
89 return HttpResponseMeta::default();
90 };
91
92 let Ok(record) = http_response.as_record() else {
93 return HttpResponseMeta::default();
94 };
95
96 let status = record
97 .get("status")
98 .and_then(|v| v.as_int().ok())
99 .map(|v| v as u16);
100
101 let headers = record
102 .get("headers")
103 .and_then(|v| v.as_record().ok())
104 .map(|headers_record| {
105 let mut map = HashMap::new();
106 for (k, v) in headers_record.iter() {
107 let header_value = match v {
108 Value::String { val, .. } => HeaderValue::Single(val.clone()),
109 Value::List { vals, .. } => {
110 let strings: Vec<String> = vals
111 .iter()
112 .filter_map(|v| v.as_str().ok())
113 .map(|s| s.to_string())
114 .collect();
115 HeaderValue::Multiple(strings)
116 }
117 _ => continue,
118 };
119 map.insert(k.clone(), header_value);
120 }
121 map
122 })
123 .unwrap_or_default();
124
125 HttpResponseMeta { status, headers }
126}
127
128pub fn value_to_bytes(value: Value) -> Vec<u8> {
129 match value {
130 Value::Nothing { .. } => Vec::new(),
131 Value::String { val, .. } => val.into_bytes(),
132 Value::Int { val, .. } => val.to_string().into_bytes(),
133 Value::Float { val, .. } => val.to_string().into_bytes(),
134 Value::Binary { val, .. } => val,
135 Value::Bool { val, .. } => val.to_string().into_bytes(),
136
137 Value::Record { val, .. } if val.get("__html").is_some() => match val.get("__html") {
139 Some(Value::String { val, .. }) => val.clone().into_bytes(),
140 _ => Vec::new(),
141 },
142
143 Value::List { .. } | Value::Record { .. } => serde_json::to_string(&value_to_json(&value))
145 .unwrap_or_else(|_| String::new())
146 .into_bytes(),
147
148 _ => todo!("value_to_bytes: {:?}", value),
149 }
150}
151
152#[cfg(test)]
153mod tests {
154 use super::value_to_json;
155 use nu_protocol::{record, Span, Value};
156
157 #[test]
160 fn unsupported_scalar_falls_back_to_string() {
161 let dur = Value::duration(3_600_000_000_000, Span::test_data());
162 assert!(matches!(value_to_json(&dur), serde_json::Value::String(_)));
163 }
164
165 #[test]
166 fn unsupported_value_nested_in_record_falls_back() {
167 let rec = Value::test_record(record! {
168 "elapsed" => Value::duration(1_000_000_000, Span::test_data()),
169 });
170 let json = value_to_json(&rec);
171 assert!(json.get("elapsed").map(|v| v.is_string()).unwrap_or(false));
172 }
173}