memberlist_proto/compression/
from_str.rs

1use core::str::FromStr;
2
3use super::CompressAlgorithm;
4
5/// An error that occurs when parsing a compress algorithm.
6#[derive(Debug, thiserror::Error)]
7#[error("invalid compress algorithm: {0}")]
8pub struct ParseCompressAlgorithmError(String);
9
10// TODO(al8n): Simplify the implementation of `FromStr` for `CompressAlgorithm`? I am lazy,
11// just repeat for each variant.
12impl FromStr for CompressAlgorithm {
13  type Err = ParseCompressAlgorithmError;
14
15  fn from_str(s: &str) -> Result<Self, Self::Err> {
16    Ok(match s.trim() {
17      "lz4" | "Lz4" | "LZ4" => {
18        #[cfg(not(feature = "lz4"))]
19        return Err(ParseCompressAlgorithmError(
20          "feature `lz4` is disabled".to_string(),
21        ));
22
23        #[cfg(feature = "lz4")]
24        Self::Lz4
25      }
26      "snappy" | "Snappy" | "SNAPPY" | "snap" | "Snap" | "SNAP" => {
27        #[cfg(not(feature = "snappy"))]
28        return Err(ParseCompressAlgorithmError(
29          "feature `snappy` is disabled".to_string(),
30        ));
31
32        #[cfg(feature = "snappy")]
33        Self::Snappy
34      }
35      val if contains(&["unknown", "Unknown", "UNKNOWN"], val) => {
36        let val = strip(&["unknown", "Unknown", "UNKNOWN"], val)
37          .unwrap()
38          .trim_start_matches("(")
39          .trim_end_matches(")");
40        Self::Unknown(
41          val
42            .parse::<u8>()
43            .map_err(|_| ParseCompressAlgorithmError(val.to_string()))?,
44        )
45      }
46      val if contains(&["brotli", "Brotli", "BROTLI"], val) => {
47        #[cfg(not(feature = "brotli"))]
48        return Err(ParseCompressAlgorithmError(
49          "feature `brotli` is disabled".to_string(),
50        ));
51
52        #[cfg(feature = "brotli")]
53        {
54          let suffix = strip(&["brotli", "Brotli", "BROTLI"], val).unwrap();
55          let val = trim_parentheses(suffix).unwrap_or("");
56
57          if val.is_empty() {
58            Self::Brotli(Default::default())
59          } else {
60            Self::Brotli(
61              val
62                .parse()
63                .map_err(|_| ParseCompressAlgorithmError(val.to_string()))?,
64            )
65          }
66        }
67      }
68      val if contains(&["zstd", "Zstd", "ZSTD"], val) => {
69        #[cfg(not(feature = "zstd"))]
70        return Err(ParseCompressAlgorithmError(
71          "feature `zstd` is disabled".to_string(),
72        ));
73
74        #[cfg(feature = "zstd")]
75        {
76          let suffix = strip(&["zstd", "Zstd", "ZSTD"], val).unwrap();
77          let val = trim_parentheses(suffix).unwrap_or("");
78
79          if val.is_empty() {
80            Self::Zstd(Default::default())
81          } else {
82            Self::Zstd(
83              val
84                .parse()
85                .map_err(|_| ParseCompressAlgorithmError(val.to_string()))?,
86            )
87          }
88        }
89      }
90      val => return Err(ParseCompressAlgorithmError(val.to_string())),
91    })
92  }
93}
94
95#[inline]
96fn strip<'a>(possible_values: &'a [&'a str], s: &'a str) -> Option<&'a str> {
97  possible_values.iter().find_map(|&m| s.strip_prefix(m))
98}
99
100#[allow(unused)]
101#[inline]
102fn trim_parentheses(s: &str) -> Option<&str> {
103  s.strip_prefix('(').and_then(|s| s.strip_suffix(')'))
104}
105
106#[inline]
107fn contains<'a>(possible_values: &'a [&'a str], s: &'a str) -> bool {
108  possible_values.iter().any(|&m| s.strip_prefix(m).is_some())
109}
110
111#[cfg(test)]
112mod tests {
113  use crate::{BrotliAlgorithm, ZstdCompressionLevel};
114
115  use super::*;
116
117  #[test]
118  fn test_compress_algorithm_from_str() {
119    #[cfg(feature = "lz4")]
120    assert_eq!(
121      "lz4".parse::<CompressAlgorithm>().unwrap(),
122      CompressAlgorithm::Lz4
123    );
124    assert_eq!(
125      "unknown(33)".parse::<CompressAlgorithm>().unwrap(),
126      CompressAlgorithm::Unknown(33)
127    );
128    assert!("unknown".parse::<CompressAlgorithm>().is_err());
129    #[cfg(feature = "brotli")]
130    assert_eq!(
131      "brotli(11, 22)".parse::<CompressAlgorithm>().unwrap(),
132      CompressAlgorithm::Brotli(BrotliAlgorithm::with_quality_and_window(
133        11.into(),
134        22.into()
135      ))
136    );
137    #[cfg(feature = "brotli")]
138    assert_eq!(
139      "brotli".parse::<CompressAlgorithm>().unwrap(),
140      CompressAlgorithm::Brotli(BrotliAlgorithm::default())
141    );
142    #[cfg(feature = "brotli")]
143    assert_eq!(
144      "brotli()".parse::<CompressAlgorithm>().unwrap(),
145      CompressAlgorithm::Brotli(BrotliAlgorithm::default())
146    );
147    #[cfg(feature = "brotli")]
148    assert!("brotli(-)".parse::<CompressAlgorithm>().is_err());
149    #[cfg(feature = "zstd")]
150    assert_eq!(
151      "zstd(3)".parse::<CompressAlgorithm>().unwrap(),
152      CompressAlgorithm::Zstd(ZstdCompressionLevel::with_level(3))
153    );
154    #[cfg(feature = "zstd")]
155    assert_eq!(
156      "zstd".parse::<CompressAlgorithm>().unwrap(),
157      CompressAlgorithm::Zstd(ZstdCompressionLevel::default())
158    );
159    #[cfg(feature = "zstd")]
160    assert_eq!(
161      "zstd()".parse::<CompressAlgorithm>().unwrap(),
162      CompressAlgorithm::Zstd(ZstdCompressionLevel::default())
163    );
164    #[cfg(feature = "zstd")]
165    assert_eq!(
166      "zstd(-)".parse::<CompressAlgorithm>().unwrap(),
167      CompressAlgorithm::Zstd(ZstdCompressionLevel::new())
168    );
169  }
170}