codetics_ocpp_core/
version.rs1use std::fmt;
14use std::str::FromStr;
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
17pub enum OcppVersion {
18 V16,
19 V201,
20 V21,
21}
22
23impl OcppVersion {
24 pub const ALL: [OcppVersion; 3] = [OcppVersion::V16, OcppVersion::V201, OcppVersion::V21];
25
26 pub const fn subprotocol(self) -> &'static str {
28 match self {
29 OcppVersion::V16 => "ocpp1.6",
30 OcppVersion::V201 => "ocpp2.0.1",
31 OcppVersion::V21 => "ocpp2.1",
32 }
33 }
34
35 pub fn from_subprotocol(s: &str) -> Option<OcppVersion> {
36 match s.trim().to_ascii_lowercase().as_str() {
39 "ocpp1.6" => Some(OcppVersion::V16),
40 "ocpp2.0.1" => Some(OcppVersion::V201),
41 "ocpp2.1" => Some(OcppVersion::V21),
42 _ => None,
43 }
44 }
45
46 pub const fn dialect(self) -> crate::ErrorDialect {
47 match self {
48 OcppVersion::V16 => crate::ErrorDialect::Ocpp16,
49 _ => crate::ErrorDialect::Ocpp2,
50 }
51 }
52
53 pub const fn as_str(self) -> &'static str {
55 match self {
56 OcppVersion::V16 => "1.6",
57 OcppVersion::V201 => "2.0.1",
58 OcppVersion::V21 => "2.1",
59 }
60 }
61}
62
63impl fmt::Display for OcppVersion {
64 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65 f.write_str(self.as_str())
66 }
67}
68
69impl FromStr for OcppVersion {
70 type Err = String;
71 fn from_str(s: &str) -> Result<Self, Self::Err> {
72 match s.trim() {
73 "1.6" | "16" | "ocpp1.6" => Ok(OcppVersion::V16),
74 "2.0.1" | "201" | "ocpp2.0.1" => Ok(OcppVersion::V201),
75 "2.1" | "21" | "ocpp2.1" => Ok(OcppVersion::V21),
76 other => Err(format!("unknown OCPP version '{other}'")),
77 }
78 }
79}
80
81pub fn negotiate<'a, I>(offered: I, acceptable: &[OcppVersion]) -> Option<(OcppVersion, &'a str)>
85where
86 I: IntoIterator<Item = &'a str>,
87{
88 offered
89 .into_iter()
90 .flat_map(|h| h.split(','))
91 .map(str::trim)
92 .filter(|t| !t.is_empty())
93 .find_map(|t| {
94 OcppVersion::from_subprotocol(t)
95 .filter(|v| acceptable.contains(v))
96 .map(|v| (v, t))
97 })
98}
99
100#[cfg(test)]
101mod tests {
102 use super::*;
103
104 #[test]
105 fn negotiation_respects_client_preference() {
106 let all = OcppVersion::ALL;
107 assert_eq!(
108 negotiate(["ocpp2.1, ocpp2.0.1, ocpp1.6"], &all),
109 Some((OcppVersion::V21, "ocpp2.1"))
110 );
111 assert_eq!(
112 negotiate(["ocpp1.6, ocpp2.0.1"], &all),
113 Some((OcppVersion::V16, "ocpp1.6"))
114 );
115 assert_eq!(
116 negotiate(["ocpp2.1", "ocpp1.6"], &[OcppVersion::V16]),
117 Some((OcppVersion::V16, "ocpp1.6"))
118 );
119 assert_eq!(negotiate(["ocpp1.5, ocpp1.8"], &all), None);
120 assert_eq!(negotiate([], &all), None);
121 assert_eq!(
122 negotiate(["OCPP1.6"], &all),
123 Some((OcppVersion::V16, "OCPP1.6"))
124 );
125 }
126}