Repository: awslabs/llrt
File: llrt_core/src/modules/http/headers.rs (Rust)

Code before:
}

impl fmt::Display for HeaderValue {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            HeaderValue::Single(s) => write!(f, "{}", s),
            HeaderValue::Multiple(v) => write!(f, "{}", v.join("")),
        }
    }
}

impl<'js> IntoJs<'js> for HeaderValue {
    fn into_js(self, ctx: &Ctx<'js>) -> Result<Value<'js>> {
        match self {
            HeaderValue::Single(s) => s.into_js(ctx),
            HeaderValue::Multiple(v) => v.join(", ").into_js(ctx),
        }
    }
}

#[derive(Clone, Default)]
#[rquickjs::class]
#[derive(rquickjs::class::Trace)]
pub struct Headers {
    #[qjs(skip_trace)]
    headers: BTreeMap<String, HeaderValue>,
}

#[methods(rename_all = "camelCase")]
impl Headers {
    #[qjs(constructor)]
    pub fn new<'js>(ctx: Ctx<'js>, init: Opt<Value<'js>>) -> Result<Self> {
        if let Some(init) = init.into_inner() {
            if init.is_array() {
                let array = init.into_array().unwrap();
                let headers = array_to_btree_map(&ctx, array)?;
                return Ok(Self::from_map(headers));
            } else if init.is_object() {
                return Self::from_value(&ctx, init);
            }
        }
        Ok(Headers::default())
    }

    pub fn append(&mut self, key: String, value: String) {
        self.insert_header_value(key, value, true);
    }

    pub fn get(&mut self, key: String) -> Option<String> {
        match self.headers.get(&key.to_lowercase()).map(|v| v.to_owned()) {
            Some(HeaderValue::Single(s)) => Some(s),

Review comment (refactor):
We can convert to Vec instead. That way we keep insertion order and is likely more performant for lower quantity of items < 1000. I see that Node uses a separate Vec for keeping track of `set-cookie` values. Not sure if that's a good approach here or if we should keep the enum or simply use a single vec and allow for duplicate set-cookie values. For example:

```javascript
// reproduction.js
const headers = new Headers();

headers.append(
  "Set-Cookie",
  "AAA=123; expires=Sun, 10-Nov-2024 12:29:35 GMT; path=/; domain=.sample.com; HttpOnly"
);
headers.append(
  "Set-Cookie",
  "BBB=456; expires=Sun, 10-Nov-2024 12:29:35 GMT; path=/; domain=.sample.com; HttpOnly"
);
headers.append("other", "abc");
headers.append("other", "cde");
console.log(Array.from(headers.entries()));
console.log(Array.from(headers.values()));
console.log(headers.get("set-cookie"));
console.log(Array.from(headers.keys()));
```

Code after:
pub struct Headers {
    #[qjs(skip_trace)]
    headers: Vec<(String, String)>,
}

#[methods(rename_all = "camelCase")]
impl Headers {
    #[qjs(constructor)]
    pub fn new<'js>(ctx: Ctx<'js>, init: Opt<Value<'js>>) -> Result<Self> {
        if let Some(init) = init.into_inner() {
            if init.is_array() {
                let array = init.into_array().unwrap();
                let headers = Self::array_to_headers(array)?;
                return Ok(Self { headers });
            } else if init.is_object() {
                return Self::from_value(&ctx, init);
            }
        }
        Ok(Self {
            headers: Vec::new(),
        })
    }

    pub fn append(&mut self, key: String, value: String) {
        let key = key.to_lowercase();
        if key == HEADERS_KEY_SET_COOKIE {
            return self.headers.push((key, value));
        }
        if let Some((_, existing_value)) = self.headers.iter_mut().find(|(k, _)| k == &key) {
            match key.as_str() {
                HEADERS_KEY_COOKIE => existing_value.push_str("; "),
                _ => existing_value.push_str(", "),
            }
            existing_value.push_str(&value);
        } else {
            self.headers.push((key, value));
        }
    }

    pub fn get(&self, key: String) -> Option<String> {
        let key = key.to_lowercase();
        if key == HEADERS_KEY_SET_COOKIE {
            let result: Vec<String> = self
                .headers
                .iter()
                .filter_map(|(k, v)| if k == &key { Some(v.clone()) } else { None })
                .collect();
            return if result.is_empty() {
                None
            } else {
                Some(result.join(", "))