1#[cfg(test)]
4mod test;
5
6#[cfg(test)]
7mod test_guess_cdr;
8
9#[cfg(test)]
10mod test_guess_tariff;
11
12#[cfg(test)]
13mod test_real_world;
14
15use std::fmt;
16
17use tracing::debug;
18
19use crate::{cdr, json, tariff, Unversioned, Versioned};
20
21pub type CdrVersion<'buf> = Version<cdr::VersionedJson<'buf>, cdr::Unversioned<'buf>>;
23
24pub(crate) fn cdr_version(doc: json::Document<'_>) -> CdrVersion<'_> {
26 guess_cdr_version(doc)
27}
28
29fn guess_cdr_version(doc: json::Document<'_>) -> CdrVersion<'_> {
31 const V211_EXCLUSIVE_FIELDS: &[&str] = &["auth_id", "location", "stop_date_time"];
34
35 const V221_EXCLUSIVE_FIELDS: &[&str] = &[
38 "authorization_reference",
39 "cdr_location", "cdr_token", "country_code", "credit",
43 "credit_reference_id",
44 "end_date_time", "home_charging_compensation",
46 "invoice_reference_id",
47 "party_id", "session_id",
49 "signed_data",
50 "total_energy_cost",
51 "total_fixed_cost",
52 "total_parking_cost",
53 "total_reservation_cost",
54 "total_time_cost",
55 ];
56
57 let Some(fields) = doc.root().as_object_fields() else {
60 return Version::Uncertain(cdr::Unversioned::new(doc));
61 };
62
63 for field in fields {
64 let key = field.key();
65
66 if key.eq_any_escape_aware(V211_EXCLUSIVE_FIELDS) {
67 return Version::Certain(cdr::VersionedJson::new(doc, crate::Version::V211));
68 } else if key.eq_any_escape_aware(V221_EXCLUSIVE_FIELDS) {
69 return Version::Certain(cdr::VersionedJson::new(doc, crate::Version::V221));
70 }
71 }
72
73 Version::Uncertain(cdr::Unversioned::new(doc))
74}
75
76pub type TariffVersion<'buf> = Version<tariff::VersionedJson<'buf>, tariff::Unversioned<'buf>>;
78
79pub(crate) fn tariff_version(doc: json::Document<'_>) -> TariffVersion<'_> {
81 guess_tariff_version(doc)
82}
83
84fn guess_tariff_version(doc: json::Document<'_>) -> TariffVersion<'_> {
86 const V221_EXCLUSIVE_FIELDS: &[&str] = &[
88 "country_code", "end_date_time",
90 "max_price",
91 "min_price",
92 "party_id", "start_date_time",
94 "type",
95 ];
96
97 const V211_V221_SHARED_FIELDS: &[&str] = &[
101 "currency",
102 "elements",
103 "energy_mix",
104 "id",
105 "last_updated",
106 "tariff_alt_text",
107 "tariff_alt_url",
108 ];
109
110 let Some(fields) = doc.root().as_object_fields() else {
113 return Version::Uncertain(tariff::Unversioned::new(doc));
114 };
115
116 let mut seen_known_field = false;
117 for field in fields {
118 let key = field.key();
119 if key.eq_any_escape_aware(V221_EXCLUSIVE_FIELDS) {
120 debug!(
121 "Tariff is v221 because of field: `{}`",
122 key.as_unescaped_str()
123 );
124 return TariffVersion::Certain(tariff::VersionedJson::new(doc, crate::Version::V221));
125 }
126 if key.eq_any_escape_aware(V211_V221_SHARED_FIELDS) {
127 seen_known_field = true;
128 }
129 }
130
131 if seen_known_field {
132 return TariffVersion::Certain(tariff::VersionedJson::new(doc, crate::Version::V211));
133 }
134
135 Version::Uncertain(tariff::Unversioned::new(doc))
136}
137
138#[derive(Debug)]
140pub enum Version<V, U>
141where
142 V: Versioned,
143 U: fmt::Debug,
144{
145 Certain(V),
147
148 Uncertain(U),
150}
151
152impl<V, U> Version<V, U>
153where
154 V: Versioned,
155 U: Unversioned<Versioned = V>,
156{
157 pub fn into_version(self) -> Version<crate::Version, ()> {
160 match self {
161 Version::Certain(v) => Version::Certain(v.version()),
162 Version::Uncertain(_) => Version::Uncertain(()),
163 }
164 }
165
166 pub fn as_version(&self) -> Version<crate::Version, ()> {
169 match self {
170 Version::Certain(v) => Version::Certain(v.version()),
171 Version::Uncertain(_) => Version::Uncertain(()),
172 }
173 }
174
175 pub fn certain_or(self, fallback: crate::Version) -> V {
178 match self {
179 Version::Certain(v) => v,
180 Version::Uncertain(u) => u.force_into_versioned(fallback),
181 }
182 }
183
184 pub fn certain_or_none(self) -> Option<V> {
186 match self {
187 Version::Certain(v) => Some(v),
188 Version::Uncertain(_) => None,
189 }
190 }
191}