sumo_types/lib.rs
1//! Reads [SUMO](https://sumo.dlr.de/) files into well-typed Rust structs.
2//!
3//! Each SUMO file format is its own Cargo feature, generated from that
4//! format's XSD; see `FEATURE_SCHEMAS` in `build.rs` for the current list.
5//! Two are implemented:
6//!
7//! | Feature | Format | Public API |
8//! |---|---|---|
9//! | `net` (default) | `.net.xml` (`netconvert` output) | `domain`, `read_network` |
10//! | `routes` | `.rou.xml` (traffic demand) | `routes` |
11//!
12//! Those are written as plain code spans, not intra-doc links, throughout
13//! this crate-level comment: it can't be `#[cfg]`-gated (a `//!` comment
14//! isn't attached to a single item), so it is rendered under every feature
15//! combination, and a link to a feature-gated item is a broken link in every
16//! build that doesn't enable it. Everything named here is reachable from the
17//! module list below under the features that provide it.
18//!
19//! `net`'s API sits at the crate root (`sumo_types::domain::Network`,
20//! `sumo_types::read_network`) because it was this crate's first, only
21//! format; that path is kept stable even though `net`'s own files live
22//! under `src/net/` internally — a module's file location and its public
23//! path are independent in Rust, and `lib.rs` re-exports `net`'s public
24//! items at the root on purpose. `routes`, added later, didn't inherit that
25//! spot: it's namespaced at `sumo_types::routes` instead, both to avoid colliding
26//! with `net`'s type names (both formats have their own `Route`-shaped
27//! concepts: `net::domain` doesn't, but a third format might) and so the
28//! module a type is imported from tells you which SUMO format it belongs
29//! to.
30//!
31//! Every format has 2 layers of types and a conversion step between them.
32//! For `net`:
33//!
34//! ```text
35//! .net.xml --(xsd-parser, at build time)--> schema --(schema_mapper)--> domain
36//! ```
37//!
38//! - `schema` (layer 1, generated at build time from the XSDs in `xsd/`)
39//! — an almost literal mirror of the SUMO schema, with no domain
40//! semantics. **Private**: it is an implementation detail, and keeping
41//! it so is what keeps `xsd-parser-types` out of this crate's public
42//! API (see the module's own comment in `lib.rs`). Shared by every
43//! enabled format (see `FEATURE_SCHEMAS` in `build.rs`), so
44//! `schema::NetType` and `schema::RoutesType` both live there when both
45//! features are on.
46//! - `schema_mapper` converts layer 1 into layer 2, interpreting SUMO's
47//! text-encoded positions, shapes, boundaries, and enumerations along the
48//! way. For example, `schema::LaneType::shape` is a raw `String`
49//! (`"0.00,0.00 100.00,0.00"`); `net`'s `schema_mapper` turns it into a
50//! `domain::Shape` (a `Vec<domain::Point>`).
51//! - `domain` (layer 2) — hand-written types independent of SUMO/XSD. This
52//! is what consumers build on: `domain` for `net`, `routes::domain`
53//! for `routes`. The two are unrelated Rust types even where SUMO's
54//! concepts overlap (a route's edges and a network's edges are both just
55//! ids, `domain::EdgeId` vs. `routes::domain::EdgeRef`) — `routes` doesn't
56//! require `net` to be enabled, so it can't borrow its types.
57//!
58//! `read_network` runs the whole pipeline for `net`, so consumers only ever
59//! have to deal with layer 2 (see its own doc comment in `net/reader.rs`
60//! for a runnable example) — marked `ignore` here rather than `no_run`
61//! because this doc comment has no `#[cfg(feature = "net")]` to gate it
62//! (crate-level `//!` docs can't be attached to a single item), so it has
63//! to compile under every feature combination, including `routes` alone:
64//!
65//! ```ignore
66//! let network = sumo_types::read_network(std::path::Path::new("city.net.xml"))?;
67//! println!("{} edges", network.edges.len());
68//! # Ok::<(), anyhow::Error>(())
69//! ```
70
71#[cfg(not(any(feature = "net", feature = "routes")))]
72compile_error!(
73 "sumo-types needs at least one format feature enabled: `net` (reads .net.xml) \
74 or `routes` (reads .rou.xml). With none of them the crate has no schema to \
75 generate and no API to offer."
76);
77
78/// Type-checks the examples in `README.md`, so they can't drift from the
79/// real API. Only exists while running doctests, and only when every format
80/// the README demonstrates is available — the file is one document, so it
81/// can't be `#[cfg]`-gated feature by feature.
82#[cfg(all(doctest, feature = "net", feature = "routes"))]
83#[doc = include_str!("../README.md")]
84pub struct ReadmeExamples;
85
86/// Types generated from the active features' XSDs by `build.rs` (see
87/// `FEATURE_SCHEMAS` there). Layer 1: an (almost) literal mirror of the
88/// schema, with no domain semantics.
89///
90/// **Private on purpose.** The generated types implement `xsd-parser-types`'
91/// traits (`WithDeserializer`, `DeserializeBytes`, ...) and name its `Error`
92/// in 173 impls, so making this module `pub` would put `xsd-parser-types`
93/// into this crate's public API. Traits are identified by their defining
94/// crate *version*: `WithDeserializer` from 0.2 and from 0.3 are different
95/// traits, so bumping that dependency would break every consumer who built
96/// on layer 1 — a breaking release forced by a dependency bump that has
97/// nothing to do with this crate's own API. Layer 2 (`domain`) doesn't
98/// mention `xsd-parser-types` anywhere, so keeping this module private is
99/// what lets the dependency stay an implementation detail.
100///
101/// Contrast [`uom`], which *is* re-exported: `domain::Lane::length` is a
102/// `Length`, so that one is public API by design rather than by accident.
103///
104/// Enum variants preserve the exact case of the XSD value (see the custom
105/// `Naming` in `build.rs`) so information like `state="M"` vs. `state="m"`
106/// isn't lost; that's why `non_camel_case_types` is disabled for the whole
107/// module.
108///
109/// `clippy::all` is disabled here for the same reason: xsd-parser's output
110/// isn't hand-written, so its style lints (redundant field names, collapsible
111/// `if`s, ...) are noise nobody can act on — and drowned out the handful of
112/// real ones in the rest of the crate.
113///
114/// `dead_code` is disabled too: every enabled format's *entire* schema is
115/// generated here (see `FEATURE_SCHEMAS` in `build.rs`), but each format's
116/// `domain`/`schema_mapper` only maps the subset it actually models — e.g.
117/// `routes` doesn't map `vTypeType`'s car-following-model choice, so
118/// xsd-parser's generated deserializer state for that choice is never
119/// constructed. That's expected, not a bug to silence one warning at a time.
120#[allow(
121 non_camel_case_types,
122 unused_variables,
123 unused_mut,
124 dead_code,
125 clippy::all
126)]
127mod schema {
128 include!(concat!(env!("OUT_DIR"), "/generated_schema.rs"));
129}
130
131/// XML plumbing shared by every format's reader: checking that a document's
132/// root element is the one that format expects, which xsd-parser's generated
133/// deserializers don't do. Only needed when there is a reader to plumb.
134#[cfg(any(feature = "net", feature = "routes"))]
135mod xml;
136
137/// `net` format's own files (`domain`, `reader`, `schema_mapper`); kept
138/// private and re-exported below so its public path (`sumo_types::domain`,
139/// `sumo_types::read_network`) doesn't have to match where the files live.
140/// See the crate docs for why `routes` isn't re-exported the same way.
141///
142/// `#[doc(hidden)]`: without it, rustdoc still generates full doc pages at
143/// `net`'s own (unreachable — `net` isn't `pub`) path, e.g.
144/// `sumo_types::net::domain::Network`, duplicating `sumo_types::domain::Network`
145/// with a URL that doesn't actually compile if used. Hiding the module
146/// stops that; the `pub use` below still inlines everything at the real
147/// public path.
148#[cfg(feature = "net")]
149#[doc(hidden)]
150mod net;
151#[cfg(feature = "net")]
152pub use net::domain;
153#[cfg(feature = "net")]
154pub use net::domain::Network;
155#[cfg(feature = "net")]
156pub use net::reader;
157#[cfg(feature = "net")]
158pub use net::reader::{read_network, read_network_from};
159#[cfg(feature = "net")]
160pub use net::schema_mapper;
161
162/// `routes` format: reads `.rou.xml`. Namespaced here (rather than
163/// re-exported at the crate root like `net`) — see the crate docs.
164#[cfg(feature = "routes")]
165pub mod routes;
166
167/// Re-exported because it appears in this crate's public API
168/// (`domain::Lane::length`, `domain::Lane::speed`, ... — code spans rather
169/// than links, because `domain` only exists under the `net` feature while
170/// this re-export is unconditional): consumers must
171/// build their `Length`/`Velocity` values with the same `uom` version this
172/// crate was compiled against, and going through `sumo_types::uom` guarantees
173/// that without them having to track the version themselves.
174pub use uom;
175
176// `xsd-parser-types` is deliberately *not* re-exported, unlike `uom`. It
177// appears nowhere in this crate's public API — only inside the private
178// `schema` module and the two readers' bodies — so re-exporting it would
179// hand back exactly the semver lock that keeping `schema` private buys:
180// a `pub use` of a dependency makes bumping that dependency a breaking
181// change for anyone who reached it through this crate.