Respect OpenAPI parameter serialization rules (#588)

This commit is contained in:
Gregory Schier
2026-08-19 22:03:39 -07:00
committed by GitHub
parent c4f96f3f11
commit 29d92e8e76
10 changed files with 631 additions and 87 deletions
+54 -16
View File
@@ -28,21 +28,59 @@ pub(crate) use duplicate_name::conflict_free_name;
const MAX_HISTORY_ITEMS: usize = 20;
use crate::models::HttpRequestHeader;
use std::collections::HashMap;
use std::collections::HashSet;
/// Deduplicate headers by name (case-insensitive), keeping the latest (most specific) value.
/// Preserves the order of first occurrence for each header name.
pub(crate) fn dedupe_headers(headers: Vec<HttpRequestHeader>) -> Vec<HttpRequestHeader> {
let mut index_by_name: HashMap<String, usize> = HashMap::new();
let mut deduped: Vec<HttpRequestHeader> = Vec::new();
for header in headers {
let key = header.name.to_lowercase();
if let Some(&idx) = index_by_name.get(&key) {
deduped[idx] = header;
} else {
index_by_name.insert(key, deduped.len());
deduped.push(header);
}
}
deduped
/// Merge a more-specific header layer over its parent. Names in the child replace
/// inherited values case-insensitively, while duplicates declared together in
/// either layer remain independent entries.
pub(crate) fn merge_headers(
mut parent: Vec<HttpRequestHeader>,
child: Vec<HttpRequestHeader>,
) -> Vec<HttpRequestHeader> {
let child_names = child.iter().map(|header| header.name.to_lowercase()).collect::<HashSet<_>>();
parent.retain(|header| !child_names.contains(&header.name.to_lowercase()));
parent.extend(child);
parent
}
#[cfg(test)]
mod tests {
use super::merge_headers;
use crate::models::HttpRequestHeader;
fn header(name: &str, value: &str) -> HttpRequestHeader {
HttpRequestHeader { name: name.to_string(), value: value.to_string(), ..Default::default() }
}
#[test]
fn preserves_duplicate_headers_declared_in_one_layer() {
let merged = merge_headers(
vec![header("Cookie", "inherited=1")],
vec![
header("Cookie", "required=1"),
header("cookie", "optional=1"),
],
);
assert_eq!(
merged.iter().map(|header| header.value.as_str()).collect::<Vec<_>>(),
vec!["required=1", "optional=1"],
);
}
#[test]
fn child_names_override_parent_names_without_affecting_other_headers() {
let merged = merge_headers(
vec![header("Accept", "*/*"), header("X-Parent", "kept")],
vec![header("accept", "application/json")],
);
assert_eq!(
merged
.iter()
.map(|header| (header.name.as_str(), header.value.as_str()))
.collect::<Vec<_>>(),
vec![("X-Parent", "kept"), ("accept", "application/json")],
);
}
}