1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
//! Guess the `Version` of the given `CDR` or tariff.
#[cfg(test)]
mod test;
#[cfg(test)]
mod test_guess_cdr;
#[cfg(test)]
mod test_guess_tariff;
#[cfg(test)]
mod test_real_world;
use std::fmt;
use tracing::debug;
use crate::{cdr, json, tariff, Unversioned, Versioned};
/// The result of calling [`cdr::infer_version`].
pub type CdrVersion<'buf> = Version<cdr::VersionedJson<'buf>, cdr::Unversioned<'buf>>;
/// Guess the `Version` of the given CDR [`json::Document`].
pub(crate) fn cdr_version(doc: json::Document<'_>) -> CdrVersion<'_> {
guess_cdr_version(doc)
}
/// Try to guess a CDR's [`Version`] and return a [`CdrVersion`] with the outcome.
fn guess_cdr_version(doc: json::Document<'_>) -> CdrVersion<'_> {
/// Fields present in `v2.1.1` CDR that do not exist in `v2.2.1`.
/// `auth_id` and `location` were replaced by `cdr_token` and `cdr_location`.
const V211_EXCLUSIVE_FIELDS: &[&str] = &["auth_id", "location", "stop_date_time"];
/// Fields introduced in `v2.2.1` CDR that do not exist in `v2.1.1`.
/// Sorted alphabetically; required fields marked for reference.
const V221_EXCLUSIVE_FIELDS: &[&str] = &[
"authorization_reference",
"cdr_location", // Required in `v2.2.1`
"cdr_token", // Required in `v2.2.1`
"country_code", // Required in `v2.2.1`
"credit",
"credit_reference_id",
"end_date_time", // Required in `v2.2.1`
"home_charging_compensation",
"invoice_reference_id",
"party_id", // Required in `v2.2.1`
"session_id",
"signed_data",
"total_energy_cost",
"total_fixed_cost",
"total_parking_cost",
"total_reservation_cost",
"total_time_cost",
];
// The `Document` is expected to be an object (see `json::parse_object`); if it is not,
// there are no fields to inspect so the version stays uncertain.
let Some(fields) = doc.root().as_object_fields() else {
return Version::Uncertain(cdr::Unversioned::new(doc));
};
for field in fields {
let key = field.key();
if key.eq_any_escape_aware(V211_EXCLUSIVE_FIELDS) {
return Version::Certain(cdr::VersionedJson::new(doc, crate::Version::V211));
} else if key.eq_any_escape_aware(V221_EXCLUSIVE_FIELDS) {
return Version::Certain(cdr::VersionedJson::new(doc, crate::Version::V221));
}
}
Version::Uncertain(cdr::Unversioned::new(doc))
}
/// The result of calling [`tariff::infer_version`].
pub type TariffVersion<'buf> = Version<tariff::VersionedJson<'buf>, tariff::Unversioned<'buf>>;
/// Guess the `Version` of the given tariff [`json::Document`].
pub(crate) fn tariff_version(doc: json::Document<'_>) -> TariffVersion<'_> {
guess_tariff_version(doc)
}
/// The private impl for detecting a tariff's version.
fn guess_tariff_version(doc: json::Document<'_>) -> TariffVersion<'_> {
/// Fields introduced in `v2.2.1` Tariff that do not exist in `v2.1.1.`.
const V221_EXCLUSIVE_FIELDS: &[&str] = &[
"country_code", // Required in `v2.2.1`
"end_date_time",
"max_price",
"min_price",
"party_id", // Required in `v2.2.1`
"start_date_time",
"type",
];
/// Fields present in both `v2.1.1` and `v2.2.1` Tariff.
/// Seeing any of these — without a `v2.2.1` exclusive field — confirms the object
/// is a `v2.1.1` tariff rather than an unrecognized document.
const V211_V221_SHARED_FIELDS: &[&str] = &[
"currency",
"elements",
"energy_mix",
"id",
"last_updated",
"tariff_alt_text",
"tariff_alt_url",
];
// The `Document` is expected to be an object (see `json::parse_object`); if it is not,
// there are no fields to inspect so the version stays uncertain.
let Some(fields) = doc.root().as_object_fields() else {
return Version::Uncertain(tariff::Unversioned::new(doc));
};
let mut seen_known_field = false;
for field in fields {
let key = field.key();
if key.eq_any_escape_aware(V221_EXCLUSIVE_FIELDS) {
debug!(
"Tariff is v221 because of field: `{}`",
key.as_unescaped_str()
);
return TariffVersion::Certain(tariff::VersionedJson::new(doc, crate::Version::V221));
}
if key.eq_any_escape_aware(V211_V221_SHARED_FIELDS) {
seen_known_field = true;
}
}
if seen_known_field {
return TariffVersion::Certain(tariff::VersionedJson::new(doc, crate::Version::V211));
}
Version::Uncertain(tariff::Unversioned::new(doc))
}
/// An OCPI object with a certain or uncertain version.
#[derive(Debug)]
pub enum Version<V, U>
where
V: Versioned,
U: fmt::Debug,
{
/// The version of the object `V` is certain.
Certain(V),
/// The version of the object `U` is uncertain.
Uncertain(U),
}
impl<V, U> Version<V, U>
where
V: Versioned,
U: Unversioned<Versioned = V>,
{
/// Convert the OCPI object into it's [`Version`](crate::Version) if it's [`Versioned`].
/// Otherwise, return `Uncertain(())`.
pub fn into_version(self) -> Version<crate::Version, ()> {
match self {
Version::Certain(v) => Version::Certain(v.version()),
Version::Uncertain(_) => Version::Uncertain(()),
}
}
/// Convert a reference to the an OCPI object into it's [`Version`](crate::Version) if it's [`Versioned`].
/// Otherwise, return `Uncertain(())`.
pub fn as_version(&self) -> Version<crate::Version, ()> {
match self {
Version::Certain(v) => Version::Certain(v.version()),
Version::Uncertain(_) => Version::Uncertain(()),
}
}
/// Return the contained OCPI object if it's [`Versioned`]. Otherwise, force convert the object
/// to the given [`Version`](crate::Version).
pub fn certain_or(self, fallback: crate::Version) -> V {
match self {
Version::Certain(v) => v,
Version::Uncertain(u) => u.force_into_versioned(fallback),
}
}
/// Return `Some` OCPI object if it's [`Versioned`]. Otherwise, return None if the object's version is uncertain.
pub fn certain_or_none(self) -> Option<V> {
match self {
Version::Certain(v) => Some(v),
Version::Uncertain(_) => None,
}
}
}