From ead97e12134251f4ed79475621ee927bd34f9a9a Mon Sep 17 00:00:00 2001 From: Valentin Haudiquet Date: Fri, 18 Sep 2026 13:22:13 +0200 Subject: [PATCH] data: consolidate the YAML embed convention into an embed_data! macro --- src/data.rs | 70 ++++++++++++++++++++++++++++++++++++++++++++++ src/distro_info.rs | 12 ++++---- src/lib.rs | 3 ++ src/put/ssh.rs | 12 ++------ src/quirks.rs | 7 ++--- 5 files changed, 84 insertions(+), 20 deletions(-) create mode 100644 src/data.rs diff --git a/src/data.rs b/src/data.rs new file mode 100644 index 0000000..7e36a3d --- /dev/null +++ b/src/data.rs @@ -0,0 +1,70 @@ +//! Embedding convention for the static reference data files (`data/*.yml`) +//! +//! Reference data that changes independently of the code — distro series +//! pointers, pinned SSH host keys, package quirks — lives in YAML files +//! under `data/` at the repo root instead of hardcoded in the source, so +//! it is updatable in one reviewable place. +//! +//! This module is deliberately not a central registry: each file is +//! embedded by the module that owns it (distro_info.rs owns +//! data/distro_info.yml, put/ssh.rs owns data/host_keys.yml, quirks.rs +//! owns data/quirks.yml) through the [`embed_data!`] macro below, so data +//! and its accessors stay together and a diff touching one domain cannot +//! half-touch another. The macro embeds the file at compile time and +//! parses it once into a `lazy_static` on first use; since the data ships +//! inside the binary, a parse failure is a build-time bug that cannot be +//! recovered from at runtime, and the macro panics on it. +//! +//! Paths and URLs in the data files carry their variable parts as `{name}` +//! placeholders, substituted with `str::replace` at the use site — no +//! template engine. + +/// Embed one YAML data file as a lazily-parsed static, following the +/// convention documented at the module level. +/// +/// Takes the visibility of the generated static (none for private, `pub` or +/// `pub(crate)`-style), its name, its struct type (which stays defined in +/// the owning module, next to its accessors) and the file path relative to +/// the invoking source file (`"../data/distro_info.yml"` from +/// `src/distro_info.rs`, `"../../data/host_keys.yml"` from +/// `src/put/ssh.rs`, ...), and expands to the house `include_str!` → +/// `lazy_static` → parse pattern — only the embed+parse boilerplate is +/// generated. +/// +/// ```ignore +/// embed_data! { +/// static ref MY_DATA: MyData = "../data/my_data.yml" +/// } +/// ``` +macro_rules! embed_data { + // Internal arm: the visibility arrives wrapped in parentheses (empty for + // private statics) because `lazy_static!` only re-matches literal + // `pub`/`pub(...)` token sequences, not an opaque forwarded `vis`. + (@expand ($($vis:tt)*) static ref $name:ident : $ty:ty = $path:literal) => { + lazy_static::lazy_static! { + // The YAML is include_str!'d at compile time and statically + // valid; if it ever failed to parse it would be a build-time bug + // that cannot be recovered from at runtime, so panicking here is + // acceptable. + $($vis)* static ref $name: $ty = serde_yaml::from_str(include_str!($path)) + .expect(concat!( + "built-in ", + $path, + " data is statically valid and must parse" + )); + } + }; + (static ref $name:ident : $ty:ty = $path:literal) => { + $crate::data::embed_data!(@expand () static ref $name : $ty = $path); + }; + (pub static ref $name:ident : $ty:ty = $path:literal) => { + $crate::data::embed_data!(@expand (pub) static ref $name : $ty = $path); + }; + (pub ($($vis:tt)+) static ref $name:ident : $ty:ty = $path:literal) => { + $crate::data::embed_data!(@expand (pub ($($vis)+)) static ref $name : $ty = $path); + }; +} + +/// Makes the macro available through the module path +/// (`use crate::data::embed_data;`) +pub(crate) use embed_data; diff --git a/src/distro_info.rs b/src/distro_info.rs index 20f52af..402199f 100644 --- a/src/distro_info.rs +++ b/src/distro_info.rs @@ -1,3 +1,4 @@ +use crate::data::embed_data; use chrono::NaiveDate; use lazy_static::lazy_static; use serde::Deserialize; @@ -43,14 +44,11 @@ struct Data { dist: std::collections::HashMap, } -const DATA_YAML: &str = include_str!("../data/distro_info.yml"); -lazy_static! { - // The YAML is include_str!'d at compile time and statically valid; if it - // ever failed to parse it would be a build-time bug that cannot be - // recovered from at runtime, so panicking here is acceptable. - static ref DATA: Data = serde_yaml::from_str(DATA_YAML) - .expect("built-in distro_info.yml data is statically valid and must parse"); +embed_data! { + static ref DATA: Data = "../data/distro_info.yml" +} +lazy_static! { // Shared HTTP client used for all outgoing plain requests: timeouts keep // a hanging remote (connect or transfer) from stalling pkh indefinitely. // The short pool idle timeout and TCP keepalive avoid reusing keep-alive diff --git a/src/lib.rs b/src/lib.rs index c2f42f1..b9a92ab 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,6 +9,9 @@ pub mod apt; pub mod build; /// Parse or edit a Debian changelog of a source package pub mod changelog; +/// Embedding convention for static reference data (`data/*.yml`), applied +/// by each owning module via the `embed_data!` macro +pub(crate) mod data; /// Build a Debian package into a binary (.deb) pub mod deb; /// Reusable Debian format primitives (control/deb822, checksums, versions, diff --git a/src/put/ssh.rs b/src/put/ssh.rs index 37edc57..8ce79a5 100644 --- a/src/put/ssh.rs +++ b/src/put/ssh.rs @@ -21,16 +21,14 @@ use std::path::{Path, PathBuf}; use std::time::Duration; use indicatif::ProgressBar; -use lazy_static::lazy_static; use log::debug; use serde::Deserialize; use sha2::{Digest, Sha256}; use ssh2::{CheckResult, HostKeyType, KnownHostFileKind, KnownHosts, Session}; +use crate::data::embed_data; use crate::ui::prompt; -const HOST_KEYS_YAML: &str = include_str!("../../data/host_keys.yml"); - /// Pinned SSH host key fingerprints, loaded from the bundled /// `host_keys.yml` data file (same pattern as `distro_info.yml`): data /// rather than code, so trust anchors are updatable without touching the @@ -42,12 +40,8 @@ struct PinnedHostKeys { fingerprints: HashMap>, } -lazy_static! { - // The YAML is include_str!'d at compile time and statically valid; if it - // ever failed to parse it would be a build-time bug that cannot be - // recovered from at runtime, so panicking here is acceptable. - static ref PINNED_HOST_KEYS: PinnedHostKeys = serde_yaml::from_str(HOST_KEYS_YAML) - .expect("built-in host_keys.yml data is statically valid and must parse"); +embed_data! { + static ref PINNED_HOST_KEYS: PinnedHostKeys = "../../data/host_keys.yml" } /// Whether `fingerprint` is one of the pinned (published) fingerprints of diff --git a/src/quirks.rs b/src/quirks.rs index 062fe85..9d0aa16 100644 --- a/src/quirks.rs +++ b/src/quirks.rs @@ -3,7 +3,7 @@ //! This module provides functionality to read quirks from a YAML file //! and apply them during pull and deb operations. -use lazy_static::lazy_static; +use crate::data::embed_data; use serde::{Deserialize, Serialize}; use std::collections::HashMap; @@ -44,9 +44,8 @@ pub struct QuirksConfig { pub quirks: HashMap, } -const QUIRKS_YAML: &str = include_str!("../data/quirks.yml"); -lazy_static! { - static ref QUIRKS_DATA: QuirksConfig = serde_yaml::from_str(QUIRKS_YAML).unwrap(); +embed_data! { + static ref QUIRKS_DATA: QuirksConfig = "../data/quirks.yml" } /// Get quirks for a specific package