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
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
//! Reads — and, with the `write` feature, writes — [SUMO](https://sumo.dlr.de/)
//! files as well-typed Rust structs.
//!
//! Each SUMO file format is its own Cargo feature, generated from that
//! format's XSD; see `FEATURE_SCHEMAS` in `build.rs` for the current list.
//! Three are implemented:
//!
//! | Feature | Format | Public API |
//! |---|---|---|
//! | `net` (default) | `.net.xml` (`netconvert` output) | `domain`, `read_network` |
//! | `routes` | `.rou.xml` (traffic demand) | `routes` |
//! | `additional` | `.add.xml` (E1/E2/E3 detectors) | `additional` |
//!
//! A fourth feature, `write`, is not a format but a direction: it adds a
//! `write_*` counterpart to every enabled format's `read_*`, running the
//! same pipeline backwards. It does nothing on its own.
//!
//! Those are written as plain code spans, not intra-doc links, throughout
//! this crate-level comment: it can't be `#[cfg]`-gated (a `//!` comment
//! isn't attached to a single item), so it is rendered under every feature
//! combination, and a link to a feature-gated item is a broken link in every
//! build that doesn't enable it. Everything named here is reachable from the
//! module list below under the features that provide it.
//!
//! `net`'s API sits at the crate root (`sumo_types::domain::Network`,
//! `sumo_types::read_network`) because it was this crate's first, only
//! format; that path is kept stable even though `net`'s own files live
//! under `src/net/` internally — a module's file location and its public
//! path are independent in Rust, and `lib.rs` re-exports `net`'s public
//! items at the root on purpose. `routes`, added later, didn't inherit that
//! spot: it's namespaced at `sumo_types::routes` instead, both to avoid colliding
//! with `net`'s type names (both formats have their own `Route`-shaped
//! concepts: `net::domain` doesn't, but a third format might) and so the
//! module a type is imported from tells you which SUMO format it belongs
//! to.
//!
//! Every format has 2 layers of types and a conversion step between them.
//! For `net`:
//!
//! ```text
//! .net.xml --(xsd-parser, at build time)--> schema --(schema_mapper)--> domain
//! ```
//!
//! - `schema` (layer 1, generated at build time from the XSDs in `xsd/`)
//! — an almost literal mirror of the SUMO schema, with no domain
//! semantics. **Private**: it is an implementation detail, and keeping
//! it so is what keeps `xsd-parser-types` out of this crate's public
//! API (see the module's own comment in `lib.rs`). Shared by every
//! enabled format (see `FEATURE_SCHEMAS` in `build.rs`), so
//! `schema::NetType` and `schema::RoutesType` both live there when both
//! features are on.
//! - `schema_mapper` converts layer 1 into layer 2, interpreting SUMO's
//! text-encoded positions, shapes, boundaries, and enumerations along the
//! way. For example, `schema::LaneType::shape` is a raw `String`
//! (`"0.00,0.00 100.00,0.00"`); `net`'s `schema_mapper` turns it into a
//! `domain::Shape` (a `Vec<domain::Point>`).
//! - `domain` (layer 2) — hand-written types independent of SUMO/XSD. This
//! is what consumers build on: `domain` for `net`, `routes::domain`
//! for `routes`. The two are unrelated Rust types even where SUMO's
//! concepts overlap (a route's edges and a network's edges are both just
//! ids, `domain::EdgeId` vs. `routes::domain::EdgeRef`) — `routes` doesn't
//! require `net` to be enabled, so it can't borrow its types.
//!
//! `read_network` runs the whole pipeline for `net`, so consumers only ever
//! have to deal with layer 2 (see its own doc comment in `net/reader.rs`
//! for a runnable example) — marked `ignore` here rather than `no_run`
//! because this doc comment has no `#[cfg(feature = "net")]` to gate it
//! (crate-level `//!` docs can't be attached to a single item), so it has
//! to compile under every feature combination, including `routes` alone:
//!
//! ```ignore
//! let network = sumo_types::read_network(std::path::Path::new("city.net.xml"))?;
//! println!("{} edges", network.edges.len());
//! # Ok::<(), sumo_types::Error>(())
//! ```
compile_error!;
/// Type-checks the examples in `README.md`, so they can't drift from the
/// real API. Only exists while running doctests, and only when every format
/// the README demonstrates is available — the file is one document, so it
/// can't be `#[cfg]`-gated feature by feature.
;
/// Types generated from the active features' XSDs by `build.rs` (see
/// `FEATURE_SCHEMAS` there). Layer 1: an (almost) literal mirror of the
/// schema, with no domain semantics.
///
/// **Private on purpose.** The generated types implement `xsd-parser-types`'
/// traits (`WithDeserializer`, `DeserializeBytes`, ...) and name its `Error`
/// in 173 impls, so making this module `pub` would put `xsd-parser-types`
/// into this crate's public API. Traits are identified by their defining
/// crate *version*: `WithDeserializer` from 0.2 and from 0.3 are different
/// traits, so bumping that dependency would break every consumer who built
/// on layer 1 — a breaking release forced by a dependency bump that has
/// nothing to do with this crate's own API. Layer 2 (`domain`) doesn't
/// mention `xsd-parser-types` anywhere, so keeping this module private is
/// what lets the dependency stay an implementation detail.
///
/// Contrast [`uom`], which *is* re-exported: `domain::Lane::length` is a
/// `Length`, so that one is public API by design rather than by accident.
///
/// Enum variants preserve the exact case of the XSD value (see the custom
/// `Naming` in `build.rs`) so information like `state="M"` vs. `state="m"`
/// isn't lost; that's why `non_camel_case_types` is disabled for the whole
/// module.
///
/// `clippy::all` is disabled here for the same reason: xsd-parser's output
/// isn't hand-written, so its style lints (redundant field names, collapsible
/// `if`s, ...) are noise nobody can act on — and drowned out the handful of
/// real ones in the rest of the crate.
///
/// `dead_code` is disabled too: every enabled format's *entire* schema is
/// generated here (see `FEATURE_SCHEMAS` in `build.rs`), but each format's
/// `domain`/`schema_mapper` only maps the subset it actually models — e.g.
/// `routes` doesn't map `vTypeType`'s car-following-model choice, so
/// xsd-parser's generated deserializer state for that choice is never
/// constructed. That's expected, not a bug to silence one warning at a time.
/// This crate's own error type, shared by every format. Unconditional (not
/// gated on a format feature) so that [`Error`]'s shape doesn't shift with
/// the consumer's feature selection.
pub use ;
/// XML plumbing shared by every format's reader: checking that a document's
/// root element is the one that format expects, which xsd-parser's generated
/// deserializers don't do. Only needed when there is a reader to plumb.
/// SUMO primitives shared by every format's `schema_mapper` — how SUMO
/// spells a boolean, encodes a number, writes a list of ids — plus the one
/// domain type that is genuinely common to all of them, [`sumo::Point`],
/// re-exported at each format's own path.
///
/// This module is what makes that sharing possible at all: it is compiled
/// whenever *any* format feature is on, so unlike `net`'s or `additional`'s
/// own modules it can hold something both must be able to name.
///
/// `dead_code` is allowed for the whole module because which helpers are
/// live depends on the active feature set: `split_ids_opt` is only reached
/// from `net`, the boolean table only from `net` and `additional`, and with
/// `routes` alone most of the module is unused. Gating each helper on the
/// features that happen to call it today would be noise that has to be
/// re-derived every time a mapper changes.
/// `net` format's own files (`domain`, `reader`, `schema_mapper`); kept
/// private and re-exported below so its public path (`sumo_types::domain`,
/// `sumo_types::read_network`) doesn't have to match where the files live.
/// See the crate docs for why `routes` isn't re-exported the same way.
///
/// `#[doc(hidden)]`: without it, rustdoc still generates full doc pages at
/// `net`'s own (unreachable — `net` isn't `pub`) path, e.g.
/// `sumo_types::net::domain::Network`, duplicating `sumo_types::domain::Network`
/// with a URL that doesn't actually compile if used. Hiding the module
/// stops that; the `pub use` below still inlines everything at the real
/// public path.
pub use domain;
pub use Network;
pub use reader;
pub use ;
pub use schema_mapper;
pub use schema_writer;
pub use writer;
pub use ;
/// `routes` format: reads `.rou.xml`. Namespaced here (rather than
/// re-exported at the crate root like `net`) — see the crate docs.
/// `additional` format: reads `.add.xml`, of which only the E1/E2/E3
/// detector definitions are modelled. Namespaced for the same reason as
/// `routes` — see the crate docs.
/// Re-exported because it appears in this crate's public API
/// (`domain::Lane::length`, `domain::Lane::speed`, ... — code spans rather
/// than links, because `domain` only exists under the `net` feature while
/// this re-export is unconditional): consumers must
/// build their `Length`/`Velocity` values with the same `uom` version this
/// crate was compiled against, and going through `sumo_types::uom` guarantees
/// that without them having to track the version themselves.
pub use uom;
// `xsd-parser-types` is deliberately *not* re-exported, unlike `uom`. It
// appears nowhere in this crate's public API — only inside the private
// `schema` module and the two readers' bodies — so re-exporting it would
// hand back exactly the semver lock that keeping `schema` private buys:
// a `pub use` of a dependency makes bumping that dependency a breaking
// change for anyone who reached it through this crate.
//
// `thiserror` is in the same position for the same reason, and is why this
// crate returns its own [`Error`] rather than an erased `anyhow::Error`:
// `thiserror` is a `derive`-only dependency that appears in no signature,
// whereas `anyhow::Error` in every `read_*`/`write_*` return type would
// have made `anyhow` part of this crate's API — the exact leak the
// paragraph above exists to prevent — while giving consumers nothing to
// match on. See `src/error.rs`.