krabka_raft/server/api_versions/
client_software.rs1#[must_use]
23pub fn is_valid_client_info(s: &str) -> bool {
24 let is_alnum = |b: u8| b.is_ascii_alphanumeric();
25 let is_interior = |b: u8| b.is_ascii_alphanumeric() || b == b'-' || b == b'.';
26 match s.as_bytes() {
27 [] => false,
28 &[only] => is_alnum(only),
29 &[first, ref interior @ .., last] => {
30 is_alnum(first) && is_alnum(last) && interior.iter().all(|&b| is_interior(b))
31 }
32 }
33}
34
35#[cfg(test)]
36mod tests {
37 use {super::*, assert2::assert};
38
39 #[yare::parameterized(
42 apache_java = { "apache-kafka-java" },
43 krabka_client = { "krabka-client-core" },
44 librdkafka = { "librdkafka" },
45 kafka_python = { "kafka-python" },
46 node_rdkafka = { "node-rdkafka" },
47 sarama = { "Sarama" },
48 version = { "3.6.2" },
49 zero_version = { "0.0.0" },
50 snapshot_version = { "1.0.0-SNAPSHOT" },
51 single_alnum = { "a" },
52 four_part_version = { "1.2.3.4" },
53 )]
54 fn valid_client_info_accepts_typical_names(s: &str) {
55 assert2::assert!(is_valid_client_info(s), "{s:?} should be valid");
56 }
57
58 #[test]
59 fn valid_client_info_rejects_empty() {
60 assert!(!is_valid_client_info(""));
61 }
62
63 #[yare::parameterized(
64 leading_hyphen = { "-leading" },
65 trailing_hyphen = { "trailing-" },
66 leading_dot = { ".dotstart" },
67 trailing_dot = { "dotend." },
68 only_hyphens = { "-only-" },
69 )]
70 fn valid_client_info_rejects_leading_or_trailing_special(s: &str) {
71 assert2::assert!(!is_valid_client_info(s), "{s:?} should be rejected");
72 }
73
74 #[yare::parameterized(
75 space = { "has space" },
76 slash = { "has/slash" },
77 backslash = { "has\\backslash" },
78 semicolon = { "has;semi" },
79 at = { "has@at" },
80 parentheses = { "has(paren)" },
81 quote = { "has\"quote" },
82 non_ascii = { "café" },
83 )]
84 fn valid_client_info_rejects_disallowed_interior_chars(s: &str) {
85 assert2::assert!(!is_valid_client_info(s), "{s:?} should be rejected");
87 }
88}