Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ jobs:
- uses: Swatinem/rust-cache@v2
with:
shared-key: clippy
- run: cargo clippy --all-targets --all-features -- -D warnings
- run: cargo clippy --all-targets --all-features -- -D warnings -W clippy::pedantic

test-matrix:
name: test (${{ matrix.os }} / ${{ matrix.toolchain }})
Expand Down
12 changes: 12 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,15 @@ codspeed-criterion-compat = { version = "2", features = ["html_reports"] }
[[bench]]
name = "route"
harness = false

# ENG-4684: package-wide pedantic baseline. `#![warn(...)]` in src/lib.rs
# reaches ONLY the library crate, so without this table the ~120 pedantic
# findings in build.rs, tests/*, benches/ and src/bin/ are gated solely by
# the `-W clippy::pedantic` flag on `.github/workflows/ci.yaml:47` — invisible
# to `.pre-commit-config.yaml`'s `cargo clippy --no-deps -- -D warnings` hook
# and to every contributor's editor. `priority = -1` makes the group rank
# below individual lint settings, so the per-file `#![allow(...)]` blocks and
# site allows still win. Nursery deliberately stays library-scoped (src/lib.rs)
# — the CI flag is pedantic-only and this table mirrors it.
[lints.clippy]
pedantic = { level = "warn", priority = -1 }
4 changes: 2 additions & 2 deletions benches/route.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
//!
//! Authored against `codspeed-criterion-compat` (a drop-in criterion
//! replacement): under `cargo bench` it behaves as plain criterion
//! (with html_reports); under `cargo codspeed` it registers these
//! benches with the CodSpeed instrumentation harness for regression
//! (with `html_reports`); under `cargo codspeed` it registers these
//! benches with the `CodSpeed` instrumentation harness for regression
//! tracking on PRs.
//!
//! Every graph is loaded via `Graph::load(N)` — the production entry
Expand Down
2 changes: 1 addition & 1 deletion build.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//! ENG-4678: compile vendored Eurostat MARNET GeoPackages into rkyv
//! ENG-4678: compile vendored Eurostat MARNET `GeoPackages` into rkyv
//! graph archives + emit `pub const EDGE_GROUPS` for the crate root.
//!
//! Reads each `vendor/eurostat-marnet/marnet_plus_{N}km.gpkg`, builds
Expand Down
4 changes: 2 additions & 2 deletions build/archive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
//!
//! File layout (matches `src/graph.rs` docs):
//! bytes 0..4 : ASCII magic b"RRG1"
//! bytes 4..8 : SCHEMA_VERSION as u32 LE
//! bytes 8.. : rkyv bytes of ArchivedGraphData
//! bytes 4..8 : `SCHEMA_VERSION` as u32 LE
//! bytes 8.. : rkyv bytes of `ArchivedGraphData`

use crate::graph::{GraphData, MAGIC, SCHEMA_VERSION};
use std::path::Path;
Expand Down
31 changes: 29 additions & 2 deletions build/csr.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,33 @@
//! Dedupe LineString endpoints into a node table and build the CSR
//! Dedupe `LineString` endpoints into a node table and build the CSR
//! adjacency for one resolution.

// ENG-4684 module-level lint posture. Inner attributes on a module FILE
// are scoped to that module, so one edit here covers every crate this
// file is compiled into: the build script (via `build.rs`'s `#[path]`
// mod) and the integration tests that re-include it the same way
// (`tests/graph_load.rs`, `tests/group_assignment.rs`,
// `tests/tampered_gpkg_panic.rs`). The alternative -- repeating each
// allow at those four crate roots -- drifts.
//
// Every `usize as u32` below narrows a node id, an edge id or a CSR row
// pointer, all of which are `u32` in the on-disk schema
// (`src/graph.rs`), so a table that overflowed the cast would already
// have failed to serialise. Every `f64 as f32` narrows a coordinate or a
// haversine weight, and `f32` is the schema's deliberate precision
// choice (~3 m at 60°N, far below the 5 km grid).
#![allow(clippy::cast_possible_truncation)]
// The remaining three fire only in the test-crate inclusion context,
// where a `pub fn` of this module becomes public API of a crate rooted
// at a test file. These are build-time internals with exactly one
// production caller each (`build/mod.rs::run`); documenting them as a
// published API surface, or annotating them `#[must_use]` for callers
// that do not exist, would be ceremony with no reader.
#![allow(
clippy::must_use_candidate,
clippy::missing_panics_doc,
clippy::missing_errors_doc
)]

use crate::build::geometry::polyline_length_km;
use crate::build::gpkg::RawEdge;
use crate::graph::{DirectedEdge, GraphData, GroupEntry, NodeCoord};
Expand All @@ -12,7 +39,7 @@ pub struct CsrBuilt {
pub edges: Vec<DirectedEdge>,
pub edge_endpoints: Vec<(u32, u32)>,
pub undirected_weights: Vec<f32>,
/// Parallel to `edge_endpoints` / `undirected_weights`: which RawEdge
/// Parallel to `edge_endpoints` / `undirected_weights`: which `RawEdge`
/// each undirected edge id came from. Used by group assignment.
pub raw_edge_index: Vec<usize>,
}
Expand Down
24 changes: 23 additions & 1 deletion build/geometry.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//! Parse GeoPackage Binary (GPB) + WKB LineString, compute haversine
//! Parse `GeoPackage` Binary (GPB) + WKB `LineString`, compute haversine
//! distances, and clip line segments against an axis-aligned bbox via
//! Liang-Barsky.
//!
Expand All @@ -7,6 +7,21 @@
//! flags to compute the WKB offset generically — robust against any
//! future re-vendor that uses a different envelope shape.

// ENG-4684: see `build/csr.rs` for why these are module-level inner
// attributes rather than crate-root allows -- this file is compiled into
// the build script and into four test crates that re-include it via
// `#[path]`.
//
// These fire only in the test-crate inclusion context, where a `pub fn`
// of this module becomes public API of a crate rooted at a test file.
// The functions are build-time geometry internals, not a published
// surface.
#![allow(
clippy::must_use_candidate,
clippy::missing_panics_doc,
clippy::missing_errors_doc
)]

/// Earth radius (km). The IUGG mean radius — sufficient precision for a
/// 5 km grid.
pub const EARTH_RADIUS_KM: f64 = 6371.0088;
Expand Down Expand Up @@ -34,6 +49,13 @@ pub fn parse_gpb_linestring(blob: &[u8]) -> Result<Vec<(f64, f64)>, String> {
return Err("GPB empty flag set; expected non-empty LineString".into());
}
let env_indicator = ((flags >> 1) & 0x07) as usize;
// `2 => 6, 3 => 6` are deliberately not merged into `2 | 3 => 6`.
// This match transcribes the GeoPackage envelope-indicator table
// (indicator 2 = XYZ, indicator 3 = XYM) row for row; the two shapes
// happen to carry the same float count but are different envelopes,
// and collapsing them would hide that correspondence from the next
// reader checking this against the spec.
#[allow(clippy::match_same_arms)]
let env_floats: usize = match env_indicator {
0 => 0,
1 => 4,
Expand Down
9 changes: 5 additions & 4 deletions build/gpkg.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
//! Raw edge data extracted from one row of a vendored `.gpkg`.
//!
//! `RawEdge` is plain data and is re-included via `#[path]` by tests
//! that exercise the build pipeline (`tests/group_assignment.rs`,
//! `tests/tampered_gpkg_panic.rs`). The GeoPackage SQLite reader
//! that exercise the build pipeline (`tests/graph_load.rs`,
//! `tests/group_assignment.rs`, `tests/tampered_gpkg_panic.rs`). The
//! `GeoPackage` `SQLite` reader
//! (`iter_edges`) lives in `build/gpkg_io.rs`; `rusqlite` is declared
//! under both `[build-dependencies]` and `[dev-dependencies]` so the
//! end-to-end tampered-gpkg test can re-include `gpkg_io.rs` and drive
//! the real reader. Tests that don't need the SQLite reader (e.g.
//! the real reader. Tests that don't need the `SQLite` reader (e.g.
//! `group_assignment.rs`) construct `RawEdge` values directly instead.

pub struct RawEdge {
pub fid: i64,
pub pass: Option<String>,
/// LineString vertices in (lng, lat) order. Length >= 2.
/// `LineString` vertices in (lng, lat) order. Length >= 2.
pub points: Vec<(f64, f64)>,
}
21 changes: 17 additions & 4 deletions build/gpkg_io.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,21 @@
//! GeoPackage SQLite reader. Depends on `rusqlite`, declared under
//! `GeoPackage` `SQLite` reader. Depends on `rusqlite`, declared under
//! both `[build-dependencies]` (for `build.rs`) and `[dev-dependencies]`
//! (so `tests/tampered_gpkg_panic.rs` can re-include this module via
//! `#[path]` and drive the real reader against a tampered `.gpkg`
//! copy). This file is never compiled into the library crate.
//! (so `tests/graph_load.rs` and `tests/tampered_gpkg_panic.rs` can
//! re-include this module via `#[path]` — the latter drives the real
//! reader against a tampered `.gpkg` copy). This file is never compiled
//! into the library crate.

// ENG-4684: see `build/csr.rs` for why this is a module-level inner
// attribute rather than a crate-root allow -- this file is re-included
// via `#[path]` by `tests/graph_load.rs` and
// `tests/tampered_gpkg_panic.rs` as well as compiled into the build
// script.
//
// `iter_edges` returns `Result<_, String>` and every error path already
// carries a human-readable message naming the failed operation; an
// `# Errors` section would restate them. The lint only fires in the
// test-crate inclusion context anyway.
#![allow(clippy::missing_errors_doc)]

use crate::build::geometry::parse_gpb_linestring;
use crate::build::gpkg::RawEdge;
Expand Down
18 changes: 15 additions & 3 deletions build/groups.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,26 @@
//! 1. For each of 12 known upstream `pass` values, every matching row
//! belongs to the corresponding public group name.
//! 2. The 13th group `menaiStrait` is derived geometrically: any edge
//! whose LineString intersects the bbox
//! whose `LineString` intersects the bbox
//! `lng ∈ [-4.20, -4.00], lat ∈ [53.13, 53.30]` (closed) using the
//! Liang-Barsky line-vs-AABB clip test.
//! 3. Empty groups at any resolution are a HARD ERROR — panics with a
//! message that names the group and resolution.
//! 4. Unknown non-null `pass` values are a HARD ERROR — panics with the
//! offending value and fid.

// ENG-4684: see `build/csr.rs` for why these are module-level inner
// attributes rather than crate-root allows -- this file is compiled into
// the build script and into the test crates that re-include it via
// `#[path]`.
//
// The `usize as u32` narrowings below produce edge ids, which are `u32`
// in the on-disk schema (`src/graph.rs::DirectedEdge::edge_id`).
#![allow(clippy::cast_possible_truncation)]
// These fire only in the test-crate inclusion context, where a `pub fn`
// of this module becomes public API of a crate rooted at a test file.
#![allow(clippy::must_use_candidate, clippy::missing_panics_doc)]

use crate::build::csr::CsrBuilt;
use crate::build::geometry::polyline_intersects_bbox;
use crate::build::gpkg::RawEdge;
Expand Down Expand Up @@ -95,8 +107,8 @@ pub fn assign_groups(raw: &[RawEdge], csr: &CsrBuilt, res_km: u32) -> Vec<GroupE
format!("pass=`{}`", PASS_GROUPS[i].0)
} else {
format!(
"menaiStrait bbox lng∈[{:.2},{:.2}] lat∈[{:.2},{:.2}]",
MENAI_LNG_MIN, MENAI_LNG_MAX, MENAI_LAT_MIN, MENAI_LAT_MAX
"menaiStrait bbox lng∈[{MENAI_LNG_MIN:.2},{MENAI_LNG_MAX:.2}] \
lat∈[{MENAI_LAT_MIN:.2},{MENAI_LAT_MAX:.2}]"
)
};
panic!(
Expand Down
17 changes: 10 additions & 7 deletions build/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,7 @@ pub fn run() {
let out_dir = PathBuf::from(std::env::var("OUT_DIR").expect("OUT_DIR set by cargo"));

for n in RESOLUTIONS {
println!(
"cargo:rerun-if-changed=vendor/eurostat-marnet/marnet_plus_{}km.gpkg",
n
);
println!("cargo:rerun-if-changed=vendor/eurostat-marnet/marnet_plus_{n}km.gpkg");
}
for f in &[
"build.rs",
Expand All @@ -57,23 +54,29 @@ pub fn run() {
String::from("// Auto-generated by build/mod.rs. Bytes-on-disk for each rkyv archive.\n");

for n in RESOLUTIONS {
let gpkg = manifest_dir.join(format!("vendor/eurostat-marnet/marnet_plus_{}km.gpkg", n));
let gpkg = manifest_dir.join(format!("vendor/eurostat-marnet/marnet_plus_{n}km.gpkg"));
let raw_edges =
gpkg_io::iter_edges(&gpkg).unwrap_or_else(|e| panic!("read {}: {e}", gpkg.display()));
let csr_built = csr::build_csr(&raw_edges);
let group_vec = groups::assign_groups(&raw_edges, &csr_built, *n);
let graph = csr_built.into_graph_with_groups(group_vec);
let archive_path = out_dir.join("data").join(format!("{}km.rkyv", n));
let archive_path = out_dir.join("data").join(format!("{n}km.rkyv"));
archive::write_archive(&archive_path, &graph)
.unwrap_or_else(|e| panic!("write {}: {e}", archive_path.display()));
let archive_len = std::fs::metadata(&archive_path)
.unwrap_or_else(|e| panic!("stat {}: {e}", archive_path.display()))
.len();
// Each const is referenced only when its matching `data-{N}km`
// feature is enabled, so the `#[allow(dead_code)]` is required.
// `clippy::unreadable_literal` is allowed for the same reason it
// cannot be fixed at the call site: these are raw byte counts in
// a generated file under `$OUT_DIR` that no human edits, and they
// are `include!`d into `src/data.rs`, so the warning is reported
// against the library with a span nobody can act on.
writeln!(
lens,
"#[allow(dead_code)]\npub(crate) const DATA_LEN_{n}KM: usize = {archive_len};",
"#[allow(dead_code, clippy::unreadable_literal)]\n\
pub(crate) const DATA_LEN_{n}KM: usize = {archive_len};",
)
.expect("write to String cannot fail");
}
Expand Down
6 changes: 3 additions & 3 deletions src/bin/rustyroute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ impl Resolution {
enum Format {
/// Compact JSON object.
Json,
/// GeoJSON FeatureCollection with one LineString feature.
/// `GeoJSON` `FeatureCollection` with one `LineString` feature.
Geojson,
/// One `lng,lat` per line.
Line,
Expand Down Expand Up @@ -285,13 +285,13 @@ fn write_route(route: &Route, format: Format, resolution_km: u32) -> io::Result<
/// Write the path as a JSON array of `[lng, lat]` positions.
///
/// `Route::coordinates` is `(lat, lng)` (see `src/loader.rs`), while
/// GeoJSON and both JSON outputs use `lng, lat`. This function is the
/// `GeoJSON` and both JSON outputs use `lng, lat`. This function is the
/// single place that swap happens.
///
/// `min_positions` pads the array by repeating the last position until
/// it holds at least that many entries. A self-route yields exactly one
/// coordinate, and RFC 7946 §3.1.4 requires a `LineString` to have two
/// or more positions, so the GeoJSON writer asks for 2 and gets a valid
/// or more positions, so the `GeoJSON` writer asks for 2 and gets a valid
/// degenerate zero-length line instead of invalid output.
fn write_coordinates<W: Write>(out: &mut W, route: &Route, min_positions: usize) -> io::Result<()> {
out.write_all(b"[")?;
Expand Down
Loading
Loading