text: /*
   * Copyright (c) Facebook, Inc. and its affiliates.
   *
   * This software may be used and distributed according to the terms of the
   * GNU General Public License version 2.
   */
  
  //! Unpacking capabilities
  
  use std::borrow::Cow;
  use std::collections::HashMap;
  
  use anyhow::{Error, Result};
  use bytes_old::{Bytes, BytesMut};
  use mercurial_types::utils::percent_encode;
  use percent_encoding::percent_decode;
  use tokio_io::codec::Decoder;
  
  #[derive(Debug, PartialEq, Eq)]
  pub struct Capabilities {
      caps: HashMap<String, Vec<String>>,
  }
  
  impl Capabilities {
      pub fn new(caps: HashMap<String, Vec<String>>) -> Self {
          Self { caps }
      }
  }
  
  /// This is a tokio_io Decoder for capabilities used f.e. in "replycaps" part of bundle2
  ///
  /// The format is as follows:
  /// <capabilities> := '' EOF | <line> ['\n' <line>]* EOF
  /// <line> := <key> | <key> '=' <values>
  /// <values> := '' | <value> [',' <value>]*
  /// <key> := `url encoded key`
  /// <value> := `url encoded value`
  ///
  /// Notice that this decoder needs to reach EOF and it decodes everything as a single item
  pub struct CapabilitiesUnpacker;
  
  impl Decoder for CapabilitiesUnpacker {
      type Item = Capabilities;
      type Error = Error;
  
      fn decode(&mut self, _buf: &mut BytesMut) -> Result<Option<Self::Item>> {
          Ok(None) // This unpacker unpacks single element, wait for EOF
      }
  
      fn decode_eof(&mut self, buf: &mut BytesMut) -> Result<Option<Self::Item>> {
          let mut caps = HashMap::new();
          for kv in buf.split(|b| b == &b'\n') {
              let mut kv = kv.splitn(2, |b| b == &b'=');
              let key = percent_decode(kv.next().expect("must have at least 1 element"))
                  .decode_utf8()?
                  .into_owned();
              let values = {
                  match kv.next() {
                      None => Vec::new(),
                      Some(values) => {
                          let res: Result<Vec<_>, _> = values
                              .split(|b| b == &b',')
                              .filter(|v| !v.is_empty())
                              .map(|v| percent_decode(v).decode_utf8().map(Cow::into_owned))
                              .collect();
                          res?
                      }
                  }
              };
              caps.insert(key, values);
          }
  
          buf.clear(); // all buf was consumed
  
          Ok(Some(Capabilities { caps }))
      }
  }
  
  pub fn encode_capabilities(caps: Capabilities) -> Bytes {
      let mut res = vec![];
      for (key, values) in caps.caps {
          let key = percent_encode(&key);
          let values = itertools::join(values.into_iter().map(|v| percent_encode(&v)), ",");
          res.push(format!("{}={}", key, values));
      }
      Bytes::from(itertools::join(res, "\n").as_str())
  }
  
  #[cfg(test)]
  mod test {
      use super::*;
      use maplit::hashmap;
  
      #[test]
      fn caps_roundtrip() {
          let caps = hashmap! {
              "key1".to_string() => vec!["value11".to_string(), "value12".to_string()],
              "key_empty".to_string() => vec![],
              "key2".to_string() => vec!["value22".to_string()],
              "weirdkey,=,=".to_string() => vec!["weirdvalue,==,".to_string(), "value".to_string()],
          };
  
          let encoded = encode_capabilities(Capabilities::new(caps.clone()));
          let mut unpacker = CapabilitiesUnpacker;
  
          let decoded = unpacker
              .decode_eof(&mut BytesMut::from(encoded))
              .unwrap()
              .unwrap();
          assert_eq!(decoded.caps, caps);
      }
  }
  