1#[cfg(test)]
2mod tests;
3
4#[cfg(test)]
5mod tests_ir;
6
7#[cfg(test)]
8mod tests_cdr_ir;
9
10use super::{
11 build, ocpi_enum, BuilderKind, Enum, Field, Integrity, Number, Object, Scalar, Schema, Str,
12 Warning,
13};
14
15use crate::{json, Caveat};
16
17const MAX_LEN_IDENTIFIER: usize = 36;
22const MAX_LEN_LONG_IDENTIFIER: usize = 39;
23const MAX_LEN_EVSE_ID: usize = 48;
24
25const MAX_LEN_CURRENCY: usize = 3;
27const MAX_LEN_PARTY_ID: usize = 3;
28const MAX_LEN_COUNTRY: usize = 3;
29const MAX_LEN_COUNTRY_CODE: usize = 2;
30const MAX_LEN_LANGUAGE: usize = 2;
31
32const MAX_LEN_LATITUDE: usize = 10;
34const MAX_LEN_LONGITUDE: usize = 11;
35const MAX_LEN_POSTAL_CODE: usize = 10;
36const MAX_LEN_ADDRESS: usize = 45;
37const MAX_LEN_CITY: usize = 45;
38const MAX_LEN_STATE: usize = 20;
39
40const MAX_LEN_NAME: usize = 255;
42const MAX_LEN_URL: usize = 255;
43const MAX_LEN_METER_ID: usize = 255;
44const MAX_LEN_REMARK: usize = 255;
45const MAX_LEN_ENERGY_MIX_NAME: usize = 64;
46const MAX_LEN_DISPLAY_TEXT: usize = 512;
47
48const MAX_LEN_NATURE: usize = 32;
50const MAX_LEN_PLAIN_DATA: usize = 512;
51const MAX_LEN_SIGNED_DATA: usize = 5000;
52const MAX_LEN_PUBLIC_KEY: usize = 512;
53const MAX_LEN_SIGNED_DATA_URL: usize = 512;
54
55ocpi_enum!(TokenType {
62 AdHocUser = "AD_HOC_USER",
63 AppUser = "APP_USER",
64 Other = "OTHER",
65 Rfid = "RFID",
66});
67
68ocpi_enum!(ConnectorFormat {
70 Socket = "SOCKET",
71 Cable = "CABLE",
72});
73
74ocpi_enum!(PowerType {
76 Ac1Phase = "AC_1_PHASE",
77 Ac2Phase = "AC_2_PHASE",
78 Ac2PhaseSplit = "AC_2_PHASE_SPLIT",
79 Ac3Phase = "AC_3_PHASE",
80 Dc = "DC",
81});
82
83ocpi_enum!(ConnectorType {
85 Chademo = "CHADEMO",
86 Chaoji = "CHAOJI",
87 DomesticA = "DOMESTIC_A",
88 DomesticB = "DOMESTIC_B",
89 DomesticC = "DOMESTIC_C",
90 DomesticD = "DOMESTIC_D",
91 DomesticE = "DOMESTIC_E",
92 DomesticF = "DOMESTIC_F",
93 DomesticG = "DOMESTIC_G",
94 DomesticH = "DOMESTIC_H",
95 DomesticI = "DOMESTIC_I",
96 DomesticJ = "DOMESTIC_J",
97 DomesticK = "DOMESTIC_K",
98 DomesticL = "DOMESTIC_L",
99 DomesticM = "DOMESTIC_M",
100 DomesticN = "DOMESTIC_N",
101 DomesticO = "DOMESTIC_O",
102 GbtAc = "GBT_AC",
103 GbtDc = "GBT_DC",
104 Iec603092Single16 = "IEC_60309_2_single_16",
105 Iec603092Three16 = "IEC_60309_2_three_16",
106 Iec603092Three32 = "IEC_60309_2_three_32",
107 Iec603092Three64 = "IEC_60309_2_three_64",
108 Iec62196T1 = "IEC_62196_T1",
109 Iec62196T1Combo = "IEC_62196_T1_COMBO",
110 Iec62196T2 = "IEC_62196_T2",
111 Iec62196T2Combo = "IEC_62196_T2_COMBO",
112 Iec62196T3A = "IEC_62196_T3A",
113 Iec62196T3C = "IEC_62196_T3C",
114 Nema520 = "NEMA_5_20",
115 Nema630 = "NEMA_6_30",
116 Nema650 = "NEMA_6_50",
117 Nema1030 = "NEMA_10_30",
118 Nema1050 = "NEMA_10_50",
119 Nema1430 = "NEMA_14_30",
120 Nema1450 = "NEMA_14_50",
121 PantographBottomUp = "PANTOGRAPH_BOTTOM_UP",
122 PantographTopDown = "PANTOGRAPH_TOP_DOWN",
123 TeslaR = "TESLA_R",
124 TeslaS = "TESLA_S",
125});
126
127ocpi_enum!(AuthMethod {
129 AuthRequest = "AUTH_REQUEST",
130 Command = "COMMAND",
131 Whitelist = "WHITELIST",
132});
133
134ocpi_enum!(CdrDimensionType {
136 Current = "CURRENT",
137 Energy = "ENERGY",
138 EnergyExport = "ENERGY_EXPORT",
139 EnergyImport = "ENERGY_IMPORT",
140 MaxCurrent = "MAX_CURRENT",
141 MinCurrent = "MIN_CURRENT",
142 MaxPower = "MAX_POWER",
143 MinPower = "MIN_POWER",
144 ParkingTime = "PARKING_TIME",
145 Power = "POWER",
146 ReservationTime = "RESERVATION_TIME",
147 StateOfCharge = "STATE_OF_CHARGE",
148 Time = "TIME",
149});
150
151ocpi_enum!(TariffDimensionType {
153 Energy = "ENERGY",
154 Flat = "FLAT",
155 ParkingTime = "PARKING_TIME",
156 Time = "TIME",
157});
158
159ocpi_enum!(DayOfWeek {
161 Monday = "MONDAY",
162 Tuesday = "TUESDAY",
163 Wednesday = "WEDNESDAY",
164 Thursday = "THURSDAY",
165 Friday = "FRIDAY",
166 Saturday = "SATURDAY",
167 Sunday = "SUNDAY",
168});
169
170ocpi_enum!(ReservationRestrictionType {
172 Reservation = "RESERVATION",
173 ReservationExpires = "RESERVATION_EXPIRES",
174});
175
176ocpi_enum!(TariffType {
178 AdHocPayment = "AD_HOC_PAYMENT",
179 ProfileCheap = "PROFILE_CHEAP",
180 ProfileFast = "PROFILE_FAST",
181 ProfileGreen = "PROFILE_GREEN",
182 Regular = "REGULAR",
183});
184
185ocpi_enum!(EnergySourceCategory {
187 Nuclear = "NUCLEAR",
188 GeneralFossil = "GENERAL_FOSSIL",
189 Coal = "COAL",
190 Gas = "GAS",
191 GeneralGreen = "GENERAL_GREEN",
192 Solar = "SOLAR",
193 Wind = "WIND",
194 Water = "WATER",
195});
196
197ocpi_enum!(EnvironmentalImpactCategory {
199 NuclearWaste = "NUCLEAR_WASTE",
200 CarbonDioxide = "CARBON_DIOXIDE",
201});
202
203static DAY_OF_WEEK: Schema = Schema::Scalar(Scalar::Enum(DayOfWeek::VARIANTS));
205
206static PRICE_OBJ: Object = Object {
207 fields: &[
208 Field::required("excl_vat", Scalar::Number),
209 Field::optional("incl_vat", Scalar::Number),
210 ],
211 kind: BuilderKind::V221Price,
212};
213
214static GEO_LOCATION_OBJ: Object = Object {
215 fields: &[
217 Field::required("latitude", Scalar::StringMax(MAX_LEN_LATITUDE)),
218 Field::required("longitude", Scalar::StringMax(MAX_LEN_LONGITUDE)),
219 ],
220 kind: BuilderKind::Ignore,
221};
222
223static CDR_TOKEN_OBJ: Object = Object {
224 fields: &[
225 Field::required("contract_id", Scalar::StringMax(MAX_LEN_IDENTIFIER)),
226 Field::required("country_code", Scalar::StringMax(MAX_LEN_COUNTRY_CODE)),
227 Field::required("party_id", Scalar::StringMax(MAX_LEN_PARTY_ID)),
228 Field::required("type", Scalar::Enum(TokenType::VARIANTS)),
229 Field::required("uid", Scalar::StringMax(MAX_LEN_IDENTIFIER)),
230 ],
231 kind: BuilderKind::Ignore,
232};
233
234static CDR_LOCATION_OBJ: Object = Object {
235 fields: &[
236 Field::required("address", Scalar::StringMax(MAX_LEN_ADDRESS)),
237 Field::required("city", Scalar::StringMax(MAX_LEN_CITY)),
238 Field::required("connector_format", Scalar::Enum(ConnectorFormat::VARIANTS)),
239 Field::required("connector_id", Scalar::StringMax(MAX_LEN_IDENTIFIER)),
240 Field::required("connector_power_type", Scalar::Enum(PowerType::VARIANTS)),
241 Field::required("connector_standard", Scalar::Enum(ConnectorType::VARIANTS)),
242 Field::required_object("coordinates", &GEO_LOCATION_OBJ),
243 Field::required("country", Scalar::StringMax(MAX_LEN_COUNTRY)),
244 Field::required("evse_id", Scalar::StringMax(MAX_LEN_EVSE_ID)),
245 Field::required("evse_uid", Scalar::StringMax(MAX_LEN_IDENTIFIER)),
246 Field::required("id", Scalar::StringMax(MAX_LEN_IDENTIFIER)),
247 Field::optional("name", Scalar::StringMax(MAX_LEN_NAME)),
248 Field::optional("postal_code", Scalar::StringMax(MAX_LEN_POSTAL_CODE)),
249 Field::optional("state", Scalar::StringMax(MAX_LEN_STATE)),
250 ],
251 kind: BuilderKind::Ignore,
252};
253
254static CDR_DIMENSION_OBJ: Object = Object {
255 fields: &[
256 Field::required("type", Scalar::Enum(CdrDimensionType::VARIANTS)),
257 Field::required("volume", Scalar::Number),
258 ],
259 kind: BuilderKind::V221CdrDimension,
260};
261static CDR_DIMENSION: Schema = Schema::Object(&CDR_DIMENSION_OBJ);
262
263static CHARGING_PERIOD_OBJ: Object = Object {
264 fields: &[
265 Field::required_array("dimensions", &CDR_DIMENSION),
266 Field::required("start_date_time", Scalar::String),
267 Field::optional("tariff_id", Scalar::StringMax(MAX_LEN_IDENTIFIER)),
268 ],
269 kind: BuilderKind::V221ChargingPeriod,
270};
271static CHARGING_PERIOD: Schema = Schema::Object(&CHARGING_PERIOD_OBJ);
272
273static SIGNED_VALUE_OBJ: Object = Object {
274 fields: &[
275 Field::required("nature", Scalar::StringMax(MAX_LEN_NATURE)),
276 Field::required("plain_data", Scalar::StringMax(MAX_LEN_PLAIN_DATA)),
277 Field::required("signed_data", Scalar::StringMax(MAX_LEN_SIGNED_DATA)),
278 ],
279 kind: BuilderKind::Ignore,
280};
281static SIGNED_VALUE: Schema = Schema::Object(&SIGNED_VALUE_OBJ);
282
283static SIGNED_DATA_OBJ: Object = Object {
287 fields: &[
288 Field::required("encoding_method", Scalar::StringMax(MAX_LEN_IDENTIFIER)),
289 Field::optional("encoding_method_version", Scalar::String),
290 Field::optional("public_key", Scalar::StringMax(MAX_LEN_PUBLIC_KEY)),
291 Field::required_array("signed_values", &SIGNED_VALUE),
292 Field::optional("url", Scalar::StringMax(MAX_LEN_SIGNED_DATA_URL)),
293 ],
294 kind: BuilderKind::Ignore,
295};
296
297static DISPLAY_TEXT_OBJ: Object = Object {
298 fields: &[
299 Field::required("language", Scalar::StringMax(MAX_LEN_LANGUAGE)),
300 Field::required("text", Scalar::StringMax(MAX_LEN_DISPLAY_TEXT)),
301 ],
302 kind: BuilderKind::Ignore,
303};
304static DISPLAY_TEXT: Schema = Schema::Object(&DISPLAY_TEXT_OBJ);
305
306static ENERGY_SOURCE_OBJ: Object = Object {
307 fields: &[
308 Field::required("percentage", Scalar::Number),
309 Field::required("source", Scalar::Enum(EnergySourceCategory::VARIANTS)),
310 ],
311 kind: BuilderKind::Ignore,
312};
313static ENERGY_SOURCE: Schema = Schema::Object(&ENERGY_SOURCE_OBJ);
314
315static ENVIRONMENTAL_IMPACT_OBJ: Object = Object {
316 fields: &[
317 Field::required("amount", Scalar::Number),
318 Field::required(
319 "category",
320 Scalar::Enum(EnvironmentalImpactCategory::VARIANTS),
321 ),
322 ],
323 kind: BuilderKind::Ignore,
324};
325static ENVIRONMENTAL_IMPACT: Schema = Schema::Object(&ENVIRONMENTAL_IMPACT_OBJ);
326
327static ENERGY_MIX_OBJ: Object = Object {
328 fields: &[
329 Field::optional(
330 "energy_product_name",
331 Scalar::StringMax(MAX_LEN_ENERGY_MIX_NAME),
332 ),
333 Field::optional_array("energy_sources", &ENERGY_SOURCE),
334 Field::optional_array("environ_impact", &ENVIRONMENTAL_IMPACT),
335 Field::required("is_green_energy", Scalar::Boolean),
336 Field::optional("supplier_name", Scalar::StringMax(MAX_LEN_ENERGY_MIX_NAME)),
337 ],
338 kind: BuilderKind::Ignore,
339};
340
341static PRICE_COMPONENT_OBJ: Object = Object {
342 fields: &[
343 Field::required("price", Scalar::Number),
344 Field::required("step_size", Scalar::Number),
345 Field::required("type", Scalar::Enum(TariffDimensionType::VARIANTS)),
346 Field::optional("vat", Scalar::Number),
347 ],
348 kind: BuilderKind::V221PriceComponent,
349};
350static PRICE_COMPONENT: Schema = Schema::Object(&PRICE_COMPONENT_OBJ);
351
352static TARIFF_RESTRICTIONS_OBJ: Object = Object {
353 fields: &[
354 Field::optional_array("day_of_week", &DAY_OF_WEEK),
355 Field::optional("end_date", Scalar::String),
356 Field::optional("end_time", Scalar::String),
357 Field::optional("max_current", Scalar::Number),
358 Field::optional("max_duration", Scalar::Number),
359 Field::optional("max_kwh", Scalar::Number),
360 Field::optional("max_power", Scalar::Number),
361 Field::optional("min_current", Scalar::Number),
362 Field::optional("min_duration", Scalar::Number),
363 Field::optional("min_kwh", Scalar::Number),
364 Field::optional("min_power", Scalar::Number),
365 Field::optional(
366 "reservation",
367 Scalar::Enum(ReservationRestrictionType::VARIANTS),
368 ),
369 Field::optional("start_date", Scalar::String),
370 Field::optional("start_time", Scalar::String),
371 ],
372 kind: BuilderKind::V221Restrictions,
373};
374
375static TARIFF_ELEMENT_OBJ: Object = Object {
376 fields: &[
377 Field::required_array("price_components", &PRICE_COMPONENT),
378 Field::optional_object("restrictions", &TARIFF_RESTRICTIONS_OBJ),
379 ],
380 kind: BuilderKind::V221Element,
381};
382static TARIFF_ELEMENT: Schema = Schema::Object(&TARIFF_ELEMENT_OBJ);
383
384static TARIFF_OBJ: Object = Object {
387 fields: &[
388 Field::required("country_code", Scalar::StringMax(MAX_LEN_COUNTRY_CODE)),
389 Field::required("currency", Scalar::StringMax(MAX_LEN_CURRENCY)),
390 Field::required_array("elements", &TARIFF_ELEMENT),
391 Field::optional("end_date_time", Scalar::String),
392 Field::optional_object("energy_mix", &ENERGY_MIX_OBJ),
393 Field::required("id", Scalar::StringMax(MAX_LEN_IDENTIFIER)),
394 Field::required("last_updated", Scalar::String),
395 Field::optional_price("max_price", &PRICE_OBJ),
396 Field::optional_price("min_price", &PRICE_OBJ),
397 Field::required("party_id", Scalar::StringMax(MAX_LEN_PARTY_ID)),
398 Field::optional("start_date_time", Scalar::String),
399 Field::optional_array("tariff_alt_text", &DISPLAY_TEXT),
400 Field::optional("tariff_alt_url", Scalar::StringMax(MAX_LEN_URL)),
401 Field::optional("type", Scalar::Enum(TariffType::VARIANTS)),
402 ],
403 kind: BuilderKind::V221Tariff,
404};
405
406static TARIFF: Schema = Schema::Object(&TARIFF_OBJ);
411
412static CDR_OBJ: Object = Object {
415 fields: &[
416 Field::required("auth_method", Scalar::Enum(AuthMethod::VARIANTS)),
417 Field::optional(
418 "authorization_reference",
419 Scalar::StringMax(MAX_LEN_IDENTIFIER),
420 ),
421 Field::required_object("cdr_location", &CDR_LOCATION_OBJ),
422 Field::required_object("cdr_token", &CDR_TOKEN_OBJ),
423 Field::required_array("charging_periods", &CHARGING_PERIOD),
424 Field::required("country_code", Scalar::StringMax(MAX_LEN_COUNTRY_CODE)),
425 Field::optional("credit", Scalar::Boolean),
426 Field::optional(
427 "credit_reference_id",
428 Scalar::StringMax(MAX_LEN_LONG_IDENTIFIER),
429 ),
430 Field::required("currency", Scalar::StringMax(MAX_LEN_CURRENCY)),
431 Field::required("end_date_time", Scalar::String),
432 Field::optional("home_charging_compensation", Scalar::Boolean),
433 Field::required("id", Scalar::StringMax(MAX_LEN_LONG_IDENTIFIER)),
434 Field::optional(
435 "invoice_reference_id",
436 Scalar::StringMax(MAX_LEN_LONG_IDENTIFIER),
437 ),
438 Field::required("last_updated", Scalar::String),
439 Field::optional("meter_id", Scalar::StringMax(MAX_LEN_METER_ID)),
440 Field::required("party_id", Scalar::StringMax(MAX_LEN_PARTY_ID)),
441 Field::optional("remark", Scalar::StringMax(MAX_LEN_REMARK)),
442 Field::optional("session_id", Scalar::StringMax(MAX_LEN_IDENTIFIER)),
443 Field::optional_object("signed_data", &SIGNED_DATA_OBJ),
444 Field::required("start_date_time", Scalar::String),
445 Field::optional_array("tariffs", &TARIFF),
446 Field::required_price("total_cost", &PRICE_OBJ),
447 Field::required("total_energy", Scalar::Number),
448 Field::optional_price("total_energy_cost", &PRICE_OBJ),
449 Field::optional_price("total_fixed_cost", &PRICE_OBJ),
450 Field::optional_price("total_parking_cost", &PRICE_OBJ),
451 Field::optional("total_parking_time", Scalar::Number),
452 Field::optional_price("total_reservation_cost", &PRICE_OBJ),
453 Field::required("total_time", Scalar::Number),
454 Field::optional_price("total_time_cost", &PRICE_OBJ),
455 ],
456 kind: BuilderKind::V221Cdr,
457};
458
459static CDR: Schema = Schema::Object(&CDR_OBJ);
465
466#[derive(Default, Clone, Debug)]
477pub(crate) struct Cdr<'buf> {
478 pub currency: Integrity<Str<'buf>>,
479 pub start_date_time: Integrity<Str<'buf>>,
480 pub end_date_time: Integrity<Str<'buf>>,
481 pub charging_periods: Integrity<Vec<Integrity<ChargingPeriod<'buf>>>>,
482 pub total_cost: Integrity<Price<'buf>>,
483 pub total_energy: Integrity<Number<'buf>>,
484 pub total_time: Integrity<Number<'buf>>,
485 pub total_parking_time: Integrity<Option<Number<'buf>>>,
486 pub total_energy_cost: Integrity<Option<Price<'buf>>>,
487 pub total_fixed_cost: Integrity<Option<Price<'buf>>>,
488 pub total_parking_cost: Integrity<Option<Price<'buf>>>,
489 pub total_reservation_cost: Integrity<Option<Price<'buf>>>,
490 pub total_time_cost: Integrity<Option<Price<'buf>>>,
491 pub tariffs: Integrity<Option<Vec<Integrity<Tariff<'buf>>>>>,
492}
493
494#[derive(Clone, Default, Debug)]
496pub(crate) struct ChargingPeriod<'buf> {
497 pub start_date_time: Integrity<Str<'buf>>,
498 pub dimensions: Integrity<Vec<Integrity<Dimension<'buf>>>>,
499}
500
501#[derive(Clone, Default, Debug)]
503pub(crate) struct Dimension<'buf> {
504 pub dimension_type: Integrity<Enum<'buf, CdrDimensionType>>,
505 pub volume: Integrity<Number<'buf>>,
506}
507
508#[derive(Clone, Default, Debug)]
510pub(crate) struct Tariff<'buf> {
511 pub country_code: Integrity<Str<'buf>>,
512 pub currency: Integrity<Str<'buf>>,
513 pub id: Integrity<Str<'buf>>,
514 pub party_id: Integrity<Str<'buf>>,
515 pub min_price: Integrity<Option<Price<'buf>>>,
516 pub max_price: Integrity<Option<Price<'buf>>>,
517 pub start_date_time: Integrity<Option<Str<'buf>>>,
518 pub end_date_time: Integrity<Option<Str<'buf>>>,
519 pub elements: Integrity<Vec<Integrity<Element<'buf>>>>,
520}
521
522#[derive(Clone, Default, Debug)]
524pub(crate) struct Element<'buf> {
525 pub price_components: Integrity<Vec<Integrity<PriceComponent<'buf>>>>,
526 pub restrictions: Integrity<Option<Restrictions<'buf>>>,
527}
528
529#[derive(Clone, Default, Debug)]
531pub(crate) struct PriceComponent<'buf> {
532 pub dimension_type: Integrity<Enum<'buf, TariffDimensionType>>,
533 pub vat: Integrity<Option<Number<'buf>>>,
534 pub price: Integrity<Number<'buf>>,
535 pub step_size: Integrity<Number<'buf>>,
536}
537
538#[derive(Clone, Debug)]
545pub(crate) enum Price<'buf> {
546 Object {
548 elem: json::Element<'buf>,
551 excl_vat: Integrity<Number<'buf>>,
552 incl_vat: Integrity<Option<Number<'buf>>>,
553 },
554 Number(Number<'buf>),
556}
557
558impl<'buf> Price<'buf> {
559 pub(super) fn new(elem: json::Element<'buf>) -> Self {
562 Self::Object {
563 elem,
564 excl_vat: Integrity::Missing,
565 incl_vat: Integrity::Missing,
566 }
567 }
568
569 pub(super) fn from_number(elem: json::Element<'buf>, digits: &'buf str) -> Self {
573 Self::Number(Number::Number { elem, digits })
574 }
575}
576
577#[derive(Clone, Default, Debug)]
579pub(crate) struct Restrictions<'buf> {
580 pub start_time: Integrity<Option<Str<'buf>>>,
581 pub end_time: Integrity<Option<Str<'buf>>>,
582 pub start_date: Integrity<Option<Str<'buf>>>,
583 pub end_date: Integrity<Option<Str<'buf>>>,
584 pub min_kwh: Integrity<Option<Number<'buf>>>,
585 pub max_kwh: Integrity<Option<Number<'buf>>>,
586 pub min_current: Integrity<Option<Number<'buf>>>,
587 pub max_current: Integrity<Option<Number<'buf>>>,
588 pub min_power: Integrity<Option<Number<'buf>>>,
589 pub max_power: Integrity<Option<Number<'buf>>>,
590 pub min_duration: Integrity<Option<Number<'buf>>>,
591 pub max_duration: Integrity<Option<Number<'buf>>>,
592 pub day_of_week: Integrity<Option<Vec<Integrity<Enum<'buf, DayOfWeek>>>>>,
593 pub reservation: Integrity<Option<Enum<'buf, ReservationRestrictionType>>>,
594}
595
596pub(crate) fn build_tariff<'buf>(doc: &json::Document<'buf>) -> Caveat<Tariff<'buf>, Warning> {
601 let (node, warnings) = super::walk(doc, &TARIFF).into_parts();
602
603 let tariff = if let build::Node::Tariff(tariff) = node {
606 tariff
607 } else {
608 Tariff::default()
609 };
610
611 Caveat::new(tariff, warnings)
612}
613
614pub(crate) fn build_cdr<'buf>(doc: &json::Document<'buf>) -> Caveat<Cdr<'buf>, Warning> {
619 let (node, warnings) = super::walk(doc, &CDR).into_parts();
620
621 let cdr = if let build::Node::Cdr(cdr) = node {
624 cdr
625 } else {
626 Cdr::default()
627 };
628
629 Caveat::new(cdr, warnings)
630}