1#[cfg(test)]
2mod tests;
3
4#[cfg(test)]
5mod tests_ir;
6
7#[cfg(test)]
8mod tests_cdr_ir;
9
10use crate::{json, schema::Warning, Caveat};
11
12use super::{
13 build, ocpi_enum, BuilderKind, Enum, Field, Integrity, Number, Object, Scalar, Schema, Str,
14};
15
16const MAX_LEN_IDENTIFIER: usize = 36;
21const MAX_LEN_LONG_IDENTIFIER: usize = 39;
22const MAX_LEN_EVSE_ID: usize = 48;
23
24const MAX_LEN_CURRENCY: usize = 3;
26const MAX_LEN_COUNTRY: usize = 3;
27const MAX_LEN_LANGUAGE: usize = 2;
28
29const MAX_LEN_LATITUDE: usize = 10;
31const MAX_LEN_LONGITUDE: usize = 11;
32const MAX_LEN_POSTAL_CODE: usize = 10;
33const MAX_LEN_ADDRESS: usize = 45;
34const MAX_LEN_CITY: usize = 45;
35
36const MAX_LEN_NAME: usize = 255;
39const MAX_LEN_URL: usize = 255;
40const MAX_LEN_METER_ID: usize = 255;
41const MAX_LEN_REMARK: usize = 255;
42const MAX_LEN_TIME_ZONE: usize = 255;
43const MAX_LEN_ENERGY_MIX_NAME: usize = 64;
44const MAX_LEN_DISPLAY_TEXT: usize = 512;
45const MAX_LEN_FLOOR_LEVEL: usize = 4;
46const MAX_LEN_PHYSICAL_REFERENCE: usize = 16;
47
48static ANY: Schema = Schema::Scalar(Scalar::Any);
51
52ocpi_enum!(ConnectorFormat {
59 Socket = "SOCKET",
60 Cable = "CABLE",
61});
62
63ocpi_enum!(PowerType {
65 Ac1Phase = "AC_1_PHASE",
66 Ac3Phase = "AC_3_PHASE",
67 Dc = "DC",
68});
69
70ocpi_enum!(ConnectorType {
72 Chademo = "CHADEMO",
73 DomesticA = "DOMESTIC_A",
74 DomesticB = "DOMESTIC_B",
75 DomesticC = "DOMESTIC_C",
76 DomesticD = "DOMESTIC_D",
77 DomesticE = "DOMESTIC_E",
78 DomesticF = "DOMESTIC_F",
79 DomesticG = "DOMESTIC_G",
80 DomesticH = "DOMESTIC_H",
81 DomesticI = "DOMESTIC_I",
82 DomesticJ = "DOMESTIC_J",
83 DomesticK = "DOMESTIC_K",
84 DomesticL = "DOMESTIC_L",
85 Iec603092Single16 = "IEC_60309_2_single_16",
86 Iec603092Three16 = "IEC_60309_2_three_16",
87 Iec603092Three32 = "IEC_60309_2_three_32",
88 Iec603092Three64 = "IEC_60309_2_three_64",
89 Iec62196T1 = "IEC_62196_T1",
90 Iec62196T1Combo = "IEC_62196_T1_COMBO",
91 Iec62196T2 = "IEC_62196_T2",
92 Iec62196T2Combo = "IEC_62196_T2_COMBO",
93 Iec62196T3A = "IEC_62196_T3A",
94 Iec62196T3C = "IEC_62196_T3C",
95 TeslaR = "TESLA_R",
96 TeslaS = "TESLA_S",
97});
98
99ocpi_enum!(Capability {
101 ChargingProfileCapable = "CHARGING_PROFILE_CAPABLE",
102 CreditCardPayable = "CREDIT_CARD_PAYABLE",
103 RemoteStartStopCapable = "REMOTE_START_STOP_CAPABLE",
104 Reservable = "RESERVABLE",
105 RfidReader = "RFID_READER",
106 UnlockCapable = "UNLOCK_CAPABLE",
107});
108
109ocpi_enum!(ParkingRestriction {
111 EvOnly = "EV_ONLY",
112 Plugged = "PLUGGED",
113 Disabled = "DISABLED",
114 Customers = "CUSTOMERS",
115 Motorcycles = "MOTORCYCLES",
116});
117
118ocpi_enum!(Status {
120 Available = "AVAILABLE",
121 Blocked = "BLOCKED",
122 Charging = "CHARGING",
123 Inoperative = "INOPERATIVE",
124 OutOfOrder = "OUTOFORDER",
125 Planned = "PLANNED",
126 Removed = "REMOVED",
127 Reserved = "RESERVED",
128 Unknown = "UNKNOWN",
129});
130
131ocpi_enum!(Facility {
133 Hotel = "HOTEL",
134 Restaurant = "RESTAURANT",
135 Cafe = "CAFE",
136 Mall = "MALL",
137 Supermarket = "SUPERMARKET",
138 Sport = "SPORT",
139 RecreationArea = "RECREATION_AREA",
140 Nature = "NATURE",
141 Museum = "MUSEUM",
142 BusStop = "BUS_STOP",
143 TaxiStand = "TAXI_STAND",
144 TrainStation = "TRAIN_STATION",
145 Airport = "AIRPORT",
146 CarpoolParking = "CARPOOL_PARKING",
147 FuelStation = "FUEL_STATION",
148 Wifi = "WIFI",
149});
150
151ocpi_enum!(LocationType {
153 OnStreet = "ON_STREET",
154 ParkingGarage = "PARKING_GARAGE",
155 UndergroundGarage = "UNDERGROUND_GARAGE",
156 ParkingLot = "PARKING_LOT",
157 Other = "OTHER",
158 Unknown = "UNKNOWN",
159});
160
161ocpi_enum!(AuthMethod {
163 AuthRequest = "AUTH_REQUEST",
164 Whitelist = "WHITELIST",
165});
166
167ocpi_enum!(CdrDimensionType {
169 Energy = "ENERGY",
170 Flat = "FLAT",
171 MaxCurrent = "MAX_CURRENT",
172 MinCurrent = "MIN_CURRENT",
173 ParkingTime = "PARKING_TIME",
174 Time = "TIME",
175});
176
177ocpi_enum!(TariffDimensionType {
179 Energy = "ENERGY",
180 Flat = "FLAT",
181 ParkingTime = "PARKING_TIME",
182 Time = "TIME",
183});
184
185ocpi_enum!(DayOfWeek {
187 Monday = "MONDAY",
188 Tuesday = "TUESDAY",
189 Wednesday = "WEDNESDAY",
190 Thursday = "THURSDAY",
191 Friday = "FRIDAY",
192 Saturday = "SATURDAY",
193 Sunday = "SUNDAY",
194});
195
196ocpi_enum!(EnergySourceCategory {
198 Nuclear = "NUCLEAR",
199 GeneralFossil = "GENERAL_FOSSIL",
200 Coal = "COAL",
201 Gas = "GAS",
202 GeneralGreen = "GENERAL_GREEN",
203 Solar = "SOLAR",
204 Wind = "WIND",
205 Water = "WATER",
206});
207
208ocpi_enum!(EnvironmentalImpactCategory {
210 NuclearWaste = "NUCLEAR_WASTE",
211 CarbonDioxide = "CARBON_DIOXIDE",
212});
213
214static CAPABILITY: Schema = Schema::Scalar(Scalar::Enum(Capability::VARIANTS));
216static PARKING_RESTRICTION: Schema = Schema::Scalar(Scalar::Enum(ParkingRestriction::VARIANTS));
217static FACILITY: Schema = Schema::Scalar(Scalar::Enum(Facility::VARIANTS));
218static DAY_OF_WEEK: Schema = Schema::Scalar(Scalar::Enum(DayOfWeek::VARIANTS));
219
220static GEO_LOCATION_OBJ: Object = Object {
221 fields: &[
223 Field::required("latitude", Scalar::StringMax(MAX_LEN_LATITUDE)),
224 Field::required("longitude", Scalar::StringMax(MAX_LEN_LONGITUDE)),
225 ],
226 kind: BuilderKind::Ignore,
227};
228
229static DISPLAY_TEXT_OBJ: Object = Object {
230 fields: &[
231 Field::required("language", Scalar::StringMax(MAX_LEN_LANGUAGE)),
232 Field::required("text", Scalar::StringMax(MAX_LEN_DISPLAY_TEXT)),
233 ],
234 kind: BuilderKind::Ignore,
235};
236static DISPLAY_TEXT: Schema = Schema::Object(&DISPLAY_TEXT_OBJ);
237
238static ENERGY_SOURCE_OBJ: Object = Object {
239 fields: &[
240 Field::required("percentage", Scalar::Number),
241 Field::required("source", Scalar::Enum(EnergySourceCategory::VARIANTS)),
242 ],
243 kind: BuilderKind::Ignore,
244};
245static ENERGY_SOURCE: Schema = Schema::Object(&ENERGY_SOURCE_OBJ);
246
247static ENVIRONMENTAL_IMPACT_OBJ: Object = Object {
250 fields: &[
251 Field::required("amount", Scalar::Number),
252 Field::required(
253 "source",
254 Scalar::Enum(EnvironmentalImpactCategory::VARIANTS),
255 ),
256 ],
257 kind: BuilderKind::Ignore,
258};
259static ENVIRONMENTAL_IMPACT: Schema = Schema::Object(&ENVIRONMENTAL_IMPACT_OBJ);
260
261static ENERGY_MIX_OBJ: Object = Object {
262 fields: &[
263 Field::optional(
264 "energy_product_name",
265 Scalar::StringMax(MAX_LEN_ENERGY_MIX_NAME),
266 ),
267 Field::optional_array("energy_sources", &ENERGY_SOURCE),
268 Field::optional_array("environ_impact", &ENVIRONMENTAL_IMPACT),
269 Field::required("is_green_energy", Scalar::Boolean),
270 Field::optional("supplier_name", Scalar::StringMax(MAX_LEN_ENERGY_MIX_NAME)),
271 ],
272 kind: BuilderKind::Ignore,
273};
274
275static CONNECTOR_OBJ: Object = Object {
278 fields: &[
279 Field::required("amperage", Scalar::Number),
280 Field::required("format", Scalar::Enum(ConnectorFormat::VARIANTS)),
281 Field::required("id", Scalar::StringMax(MAX_LEN_IDENTIFIER)),
282 Field::required("last_updated", Scalar::String),
283 Field::required("power_type", Scalar::Enum(PowerType::VARIANTS)),
284 Field::required("standard", Scalar::Enum(ConnectorType::VARIANTS)),
285 Field::optional("tariff_id", Scalar::StringMax(MAX_LEN_IDENTIFIER)),
286 Field::optional("terms_and_conditions", Scalar::StringMax(MAX_LEN_URL)),
287 Field::required("voltage", Scalar::Number),
288 ],
289 kind: BuilderKind::Ignore,
290};
291static CONNECTOR: Schema = Schema::Object(&CONNECTOR_OBJ);
292
293static EVSE_OBJ: Object = Object {
296 fields: &[
297 Field::optional_array("capabilities", &CAPABILITY),
298 Field::required_array("connectors", &CONNECTOR),
299 Field::optional_object("coordinates", &GEO_LOCATION_OBJ),
300 Field::optional_array("directions", &DISPLAY_TEXT),
301 Field::optional("evse_id", Scalar::StringMax(MAX_LEN_EVSE_ID)),
302 Field::optional("floor_level", Scalar::StringMax(MAX_LEN_FLOOR_LEVEL)),
303 Field::optional_array("images", &ANY),
304 Field::required("last_updated", Scalar::String),
305 Field::optional_array("parking_restrictions", &PARKING_RESTRICTION),
306 Field::optional(
307 "physical_reference",
308 Scalar::StringMax(MAX_LEN_PHYSICAL_REFERENCE),
309 ),
310 Field::required("status", Scalar::Enum(Status::VARIANTS)),
311 Field::optional_array("status_schedule", &ANY),
312 Field::required("uid", Scalar::StringMax(MAX_LEN_LONG_IDENTIFIER)),
313 ],
314 kind: BuilderKind::Ignore,
315};
316static EVSE: Schema = Schema::Object(&EVSE_OBJ);
317
318static LOCATION_OBJ: Object = Object {
323 fields: &[
324 Field::required("address", Scalar::StringMax(MAX_LEN_ADDRESS)),
325 Field::optional("charging_when_closed", Scalar::Boolean),
326 Field::required("city", Scalar::StringMax(MAX_LEN_CITY)),
327 Field::required_object("coordinates", &GEO_LOCATION_OBJ),
328 Field::required("country", Scalar::StringMax(MAX_LEN_COUNTRY)),
329 Field::optional_array("directions", &DISPLAY_TEXT),
330 Field::optional_object("energy_mix", &ENERGY_MIX_OBJ),
331 Field::optional_array("evses", &EVSE),
332 Field::optional_array("facilities", &FACILITY),
333 Field::required("id", Scalar::StringMax(MAX_LEN_LONG_IDENTIFIER)),
334 Field::optional_array("images", &ANY),
335 Field::required("last_updated", Scalar::String),
336 Field::optional("name", Scalar::StringMax(MAX_LEN_NAME)),
337 Field::optional("opening_times", Scalar::Any),
338 Field::optional("operator", Scalar::Any),
339 Field::optional("owner", Scalar::Any),
340 Field::required("postal_code", Scalar::StringMax(MAX_LEN_POSTAL_CODE)),
341 Field::optional_array("related_locations", &ANY),
342 Field::optional("suboperator", Scalar::Any),
343 Field::optional("time_zone", Scalar::StringMax(MAX_LEN_TIME_ZONE)),
344 Field::required("type", Scalar::Enum(LocationType::VARIANTS)),
345 ],
346 kind: BuilderKind::Ignore,
347};
348
349static CDR_DIMENSION_OBJ: Object = Object {
350 fields: &[
351 Field::required("type", Scalar::Enum(CdrDimensionType::VARIANTS)),
352 Field::required("volume", Scalar::Number),
353 ],
354 kind: BuilderKind::V211CdrDimension,
355};
356static CDR_DIMENSION: Schema = Schema::Object(&CDR_DIMENSION_OBJ);
357
358static CHARGING_PERIOD_OBJ: Object = Object {
361 fields: &[
362 Field::required_array("dimensions", &CDR_DIMENSION),
363 Field::required("start_date_time", Scalar::String),
364 ],
365 kind: BuilderKind::V211ChargingPeriod,
366};
367static CHARGING_PERIOD: Schema = Schema::Object(&CHARGING_PERIOD_OBJ);
368
369static PRICE_COMPONENT_OBJ: Object = Object {
372 fields: &[
373 Field::required("price", Scalar::Number),
374 Field::required("step_size", Scalar::Number),
375 Field::required("type", Scalar::Enum(TariffDimensionType::VARIANTS)),
376 ],
377 kind: BuilderKind::V211PriceComponent,
378};
379static PRICE_COMPONENT: Schema = Schema::Object(&PRICE_COMPONENT_OBJ);
380
381static TARIFF_RESTRICTIONS_OBJ: Object = Object {
385 fields: &[
386 Field::optional_array("day_of_week", &DAY_OF_WEEK),
387 Field::optional("end_date", Scalar::String),
388 Field::optional("end_time", Scalar::String),
389 Field::optional("max_duration", Scalar::Number),
390 Field::optional("max_kwh", Scalar::Number),
391 Field::optional("max_power", Scalar::Number),
392 Field::optional("min_duration", Scalar::Number),
393 Field::optional("min_kwh", Scalar::Number),
394 Field::optional("min_power", Scalar::Number),
395 Field::optional("start_date", Scalar::String),
396 Field::optional("start_time", Scalar::String),
397 ],
398 kind: BuilderKind::V211Restrictions,
399};
400
401static TARIFF_ELEMENT_OBJ: Object = Object {
402 fields: &[
403 Field::required_array("price_components", &PRICE_COMPONENT),
404 Field::optional_object("restrictions", &TARIFF_RESTRICTIONS_OBJ),
405 ],
406 kind: BuilderKind::V211Element,
407};
408static TARIFF_ELEMENT: Schema = Schema::Object(&TARIFF_ELEMENT_OBJ);
409
410static TARIFF_OBJ: Object = Object {
414 fields: &[
415 Field::required("currency", Scalar::StringMax(MAX_LEN_CURRENCY)),
416 Field::required_array("elements", &TARIFF_ELEMENT),
417 Field::optional_object("energy_mix", &ENERGY_MIX_OBJ),
418 Field::required("id", Scalar::StringMax(MAX_LEN_IDENTIFIER)),
419 Field::required("last_updated", Scalar::String),
420 Field::optional_array("tariff_alt_text", &DISPLAY_TEXT),
421 Field::optional("tariff_alt_url", Scalar::StringMax(MAX_LEN_URL)),
422 ],
423 kind: BuilderKind::V211Tariff,
424};
425
426static TARIFF: Schema = Schema::Object(&TARIFF_OBJ);
431
432static CDR_OBJ: Object = Object {
442 fields: &[
443 Field::required("auth_id", Scalar::StringMax(MAX_LEN_IDENTIFIER)),
444 Field::required("auth_method", Scalar::Enum(AuthMethod::VARIANTS)),
445 Field::required_array("charging_periods", &CHARGING_PERIOD),
446 Field::required("currency", Scalar::StringMax(MAX_LEN_CURRENCY)),
447 Field::required("id", Scalar::StringMax(MAX_LEN_IDENTIFIER)),
448 Field::required("last_updated", Scalar::String),
449 Field::required_object("location", &LOCATION_OBJ),
450 Field::optional("meter_id", Scalar::StringMax(MAX_LEN_METER_ID)),
451 Field::optional("remark", Scalar::StringMax(MAX_LEN_REMARK)),
452 Field::required("start_date_time", Scalar::String),
453 Field::required("stop_date_time", Scalar::String),
454 Field::optional_array("tariffs", &TARIFF),
455 Field::required("total_cost", Scalar::Number),
456 Field::required("total_energy", Scalar::Number),
457 Field::optional("total_parking_time", Scalar::Number),
458 Field::required("total_time", Scalar::Number),
459 ],
460 kind: BuilderKind::V211Cdr,
461};
462
463static CDR: Schema = Schema::Object(&CDR_OBJ);
469
470#[derive(Clone, Default, Debug)]
479pub(crate) struct Cdr<'buf> {
480 pub currency: Integrity<Str<'buf>>,
481 pub start_date_time: Integrity<Str<'buf>>,
482 pub stop_date_time: Integrity<Str<'buf>>,
483 pub charging_periods: Integrity<Vec<Integrity<ChargingPeriod<'buf>>>>,
484 pub total_cost: Integrity<Number<'buf>>,
485 pub total_energy: Integrity<Number<'buf>>,
486 pub total_time: Integrity<Number<'buf>>,
487 pub total_parking_time: Integrity<Option<Number<'buf>>>,
488 pub tariffs: Integrity<Option<Vec<Integrity<Tariff<'buf>>>>>,
489}
490
491#[derive(Clone, Default, Debug)]
493pub(crate) struct ChargingPeriod<'buf> {
494 pub start_date_time: Integrity<Str<'buf>>,
495 pub dimensions: Integrity<Vec<Integrity<Dimension<'buf>>>>,
496}
497
498#[derive(Clone, Default, Debug)]
500pub(crate) struct Dimension<'buf> {
501 pub dimension_type: Integrity<Enum<'buf, CdrDimensionType>>,
502 pub volume: Integrity<Number<'buf>>,
503}
504
505#[derive(Clone, Default, Debug)]
515pub(crate) struct Tariff<'buf> {
516 pub currency: Integrity<Str<'buf>>,
517 pub id: Integrity<Str<'buf>>,
518 pub elements: Integrity<Vec<Integrity<Element<'buf>>>>,
519}
520
521#[derive(Clone, Default, Debug)]
523pub(crate) struct Element<'buf> {
524 pub price_components: Integrity<Vec<Integrity<PriceComponent<'buf>>>>,
525 pub restrictions: Integrity<Option<Restrictions<'buf>>>,
526}
527
528#[derive(Clone, Default, Debug)]
530pub(crate) struct PriceComponent<'buf> {
531 pub dimension_type: Integrity<Enum<'buf, TariffDimensionType>>,
532 pub price: Integrity<Number<'buf>>,
533 pub step_size: Integrity<Number<'buf>>,
534}
535
536#[derive(Clone, Default, Debug)]
539pub(crate) struct Restrictions<'buf> {
540 pub start_time: Integrity<Option<Str<'buf>>>,
541 pub end_time: Integrity<Option<Str<'buf>>>,
542 pub start_date: Integrity<Option<Str<'buf>>>,
543 pub end_date: Integrity<Option<Str<'buf>>>,
544 pub min_kwh: Integrity<Option<Number<'buf>>>,
545 pub max_kwh: Integrity<Option<Number<'buf>>>,
546 pub min_power: Integrity<Option<Number<'buf>>>,
547 pub max_power: Integrity<Option<Number<'buf>>>,
548 pub min_duration: Integrity<Option<Number<'buf>>>,
549 pub max_duration: Integrity<Option<Number<'buf>>>,
550 pub day_of_week: Integrity<Option<Vec<Integrity<Enum<'buf, DayOfWeek>>>>>,
551}
552
553pub(crate) fn build_tariff<'buf>(doc: &json::Document<'buf>) -> Caveat<Tariff<'buf>, Warning> {
558 let (node, warnings) = super::walk(doc, &TARIFF).into_parts();
559
560 let tariff = if let build::Node::V211Tariff(tariff) = node {
563 tariff
564 } else {
565 Tariff::default()
566 };
567
568 Caveat::new(tariff, warnings)
569}
570
571pub(crate) fn build_cdr<'buf>(doc: &json::Document<'buf>) -> Caveat<Cdr<'buf>, Warning> {
576 let (node, warnings) = super::walk(doc, &CDR).into_parts();
577
578 let cdr = if let build::Node::V211Cdr(cdr) = node {
581 cdr
582 } else {
583 Cdr::default()
584 };
585
586 Caveat::new(cdr, warnings)
587}