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
//! Strict-decoding support for BO4E payloads.
//!
//! Every BO4E enum carries an `Unknown` forward-compatibility catch-all, so the
//! lenient `serde` deserialization path never fails on an unrecognized wire value
//! — it maps to `Unknown`. That is the right default for forward-compatibility,
//! but the wrong default at an **ingest boundary** that must reject typos, legacy
//! codes, or values from a newer schema.
//!
//! The [`Bo4eStrict`](crate::Bo4eStrict) trait — implemented by every generated BO,
//! COM, enum, and by `AnyBo` — walks a value **recursively** and reports the
//! JSON-path of every enum field that decoded to `Unknown`. This turns the MaKo
//! "round-trip as validation" pattern into an actually-strict one:
//!
//! ```
//! # #[cfg(feature = "json")] {
//! use rubo4e::{Bo4eStrict, current::Messlokation};
//!
//! // `sparte` carries a value this schema version does not define.
//! let body = r#"{"messlokationsId":"DE0123456789012345678901234567890","sparte":"PLASMA"}"#;
//! let melo: Messlokation = serde_json::from_str(body).unwrap(); // lenient decode
//!
//! // One call finds it, wherever it sits in the tree:
//! let err = melo.ensure_known_enums().unwrap_err();
//! assert_eq!(err.paths, ["sparte"]);
//! # }
//! ```
//!
//! One call replaces the hand-written `field == T::Unknown` checks scattered across
//! every handler.
/// The set of JSON-paths at which a payload holds an out-of-schema (`Unknown`)
/// enum value, produced by [`Bo4eStrict::ensure_known_enums`](crate::Bo4eStrict::ensure_known_enums).
///
/// Paths are dotted, with array indices in brackets, relative to the value that
/// was checked — e.g. `zaehler[0].zaehlertyp` or `bilanzierung.aggregationsverantwortung`.
/// Joins a child field name onto a parent JSON-path.
///
/// Used by generated [`Bo4eStrict`](crate::Bo4eStrict) impls; rarely called
/// directly. The root path is the empty string, so the first segment carries no
/// leading dot.
/// Joins an **extension** key onto a parent JSON-path.
///
/// Unlike [`field_path`], the key here comes off the wire rather than out of the
/// schema, so it can be any string up to
/// [`MAX_EXTENSION_KEY_LEN`](crate::json::MAX_EXTENSION_KEY_LEN) bytes — dots,
/// brackets and quotes included. A key of `"a.b"` rendered as `parent.a.b` would
/// read as two nested fields that do not exist, so anything that is not a plain
/// `[A-Za-z0-9_]` identifier is bracket-quoted instead:
///
/// ```
/// # #[cfg(all(feature = "versioned", feature = "json"))] {
/// use rubo4e::strict::extension_path;
///
/// assert_eq!(extension_path("", "meineId"), "meineId");
/// assert_eq!(extension_path("adresse", "meineId"), "adresse.meineId");
/// // Not an identifier — quoted, so the key stays one segment.
/// assert_eq!(extension_path("adresse", "a.b"), r#"adresse["a.b"]"#);
/// assert_eq!(extension_path("", "a.b"), r#"["a.b"]"#);
/// assert_eq!(extension_path("x", "he\"y"), r#"x["he\"y"]"#);
/// # }
/// ```
///
/// Used by generated [`Bo4eExtensions`](crate::json::Bo4eExtensions) impls.
/// Joins an array index onto a parent JSON-path (`parent[i]`).
///
/// Used by generated [`Bo4eStrict`](crate::Bo4eStrict) impls; rarely called directly.