Skip to main content

http_nu/
response.rs

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/// HTTP response metadata extracted from pipeline metadata (`http.response`)
13#[derive(Clone, Debug, Default)]
14pub struct HttpResponseMeta {
15    pub status: Option<u16>,
16    pub headers: HashMap<String, HeaderValue>,
17}
18
19/// Special response types that bypass normal body handling
20#[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        _ => todo!(),
72    }
73}
74
75/// Extract HTTP response metadata from pipeline metadata's `http.response` field
76pub fn extract_http_response_meta(
77    metadata: Option<&nu_protocol::PipelineMetadata>,
78) -> HttpResponseMeta {
79    let Some(meta) = metadata else {
80        return HttpResponseMeta::default();
81    };
82
83    let Some(http_response) = meta.custom.get("http.response") else {
84        return HttpResponseMeta::default();
85    };
86
87    let Ok(record) = http_response.as_record() else {
88        return HttpResponseMeta::default();
89    };
90
91    let status = record
92        .get("status")
93        .and_then(|v| v.as_int().ok())
94        .map(|v| v as u16);
95
96    let headers = record
97        .get("headers")
98        .and_then(|v| v.as_record().ok())
99        .map(|headers_record| {
100            let mut map = HashMap::new();
101            for (k, v) in headers_record.iter() {
102                let header_value = match v {
103                    Value::String { val, .. } => HeaderValue::Single(val.clone()),
104                    Value::List { vals, .. } => {
105                        let strings: Vec<String> = vals
106                            .iter()
107                            .filter_map(|v| v.as_str().ok())
108                            .map(|s| s.to_string())
109                            .collect();
110                        HeaderValue::Multiple(strings)
111                    }
112                    _ => continue,
113                };
114                map.insert(k.clone(), header_value);
115            }
116            map
117        })
118        .unwrap_or_default();
119
120    HttpResponseMeta { status, headers }
121}
122
123pub fn value_to_bytes(value: Value) -> Vec<u8> {
124    match value {
125        Value::Nothing { .. } => Vec::new(),
126        Value::String { val, .. } => val.into_bytes(),
127        Value::Int { val, .. } => val.to_string().into_bytes(),
128        Value::Float { val, .. } => val.to_string().into_bytes(),
129        Value::Binary { val, .. } => val,
130        Value::Bool { val, .. } => val.to_string().into_bytes(),
131
132        // Records with __html field are unwrapped to HTML string
133        Value::Record { val, .. } if val.get("__html").is_some() => match val.get("__html") {
134            Some(Value::String { val, .. }) => val.clone().into_bytes(),
135            _ => Vec::new(),
136        },
137
138        // Both Lists and Records are encoded as JSON
139        Value::List { .. } | Value::Record { .. } => serde_json::to_string(&value_to_json(&value))
140            .unwrap_or_else(|_| String::new())
141            .into_bytes(),
142
143        _ => todo!("value_to_bytes: {:?}", value),
144    }
145}