use alloc::string::{String, ToString};
use alloc::vec::Vec;
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Capabilities {
items: Vec<(String, Option<String>)>,
}
impl Capabilities {
pub fn new() -> Self {
Capabilities::default()
}
pub fn parse(s: &str) -> Self {
let mut items = Vec::new();
for token in s.split(' ').filter(|t| !t.is_empty()) {
match token.split_once('=') {
Some((k, v)) => items.push((k.to_string(), Some(v.to_string()))),
None => items.push((token.to_string(), None)),
}
}
Capabilities { items }
}
pub fn has(&self, key: &str) -> bool {
self.items.iter().any(|(k, _)| k == key)
}
pub fn get(&self, key: &str) -> Option<&str> {
self.items
.iter()
.find(|(k, _)| k == key)
.and_then(|(_, v)| v.as_deref())
}
pub fn add_flag(&mut self, key: &str) {
self.items.push((key.to_string(), None));
}
pub fn add_value(&mut self, key: &str, value: &str) {
self.items.push((key.to_string(), Some(value.to_string())));
}
pub fn to_wire(&self) -> String {
let mut parts: Vec<String> = Vec::with_capacity(self.items.len());
for (k, v) in &self.items {
match v {
Some(v) => parts.push(alloc::format!("{k}={v}")),
None => parts.push(k.clone()),
}
}
parts.join(" ")
}
pub fn negotiate_flags(&self, wanted: &[&str]) -> Capabilities {
let mut out = Capabilities::new();
for &w in wanted {
if self.has(w) {
out.add_flag(w);
}
}
out
}
pub fn object_format(&self) -> Option<crate::oid::HashAlgo> {
match self.get("object-format") {
Some("sha1") => Some(crate::oid::HashAlgo::Sha1),
Some("sha256") => Some(crate::oid::HashAlgo::Sha256),
_ => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn negotiate_flags_is_the_intersection() {
let server = Capabilities::parse("multi_ack ofs-delta agent=git/2.40");
let agreed = server.negotiate_flags(&["ofs-delta", "side-band-64k", "multi_ack"]);
assert!(agreed.has("ofs-delta"));
assert!(agreed.has("multi_ack"));
assert!(!agreed.has("side-band-64k")); assert!(!agreed.has("agent")); assert_eq!(agreed.to_wire(), "ofs-delta multi_ack");
}
#[test]
fn parse_and_query() {
let caps = Capabilities::parse(
"multi_ack thin-pack ofs-delta agent=git/2.40 object-format=sha256",
);
assert!(caps.has("thin-pack"));
assert!(caps.has("ofs-delta"));
assert_eq!(caps.get("agent"), Some("git/2.40"));
assert_eq!(caps.object_format(), Some(crate::oid::HashAlgo::Sha256));
assert!(!caps.has("missing"));
}
}