data: consolidate the YAML embed convention into an embed_data! macro

This commit is contained in:
2026-09-18 13:22:13 +02:00
parent 6e5ecd2f45
commit ead97e1213
5 changed files with 84 additions and 20 deletions
+70
View File
@@ -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;
+5 -7
View File
@@ -1,3 +1,4 @@
use crate::data::embed_data;
use chrono::NaiveDate; use chrono::NaiveDate;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use serde::Deserialize; use serde::Deserialize;
@@ -43,14 +44,11 @@ struct Data {
dist: std::collections::HashMap<String, DistData>, dist: std::collections::HashMap<String, DistData>,
} }
const DATA_YAML: &str = include_str!("../data/distro_info.yml"); embed_data! {
lazy_static! { static ref DATA: Data = "../data/distro_info.yml"
// 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");
lazy_static! {
// Shared HTTP client used for all outgoing plain requests: timeouts keep // Shared HTTP client used for all outgoing plain requests: timeouts keep
// a hanging remote (connect or transfer) from stalling pkh indefinitely. // a hanging remote (connect or transfer) from stalling pkh indefinitely.
// The short pool idle timeout and TCP keepalive avoid reusing keep-alive // The short pool idle timeout and TCP keepalive avoid reusing keep-alive
+3
View File
@@ -9,6 +9,9 @@ pub mod apt;
pub mod build; pub mod build;
/// Parse or edit a Debian changelog of a source package /// Parse or edit a Debian changelog of a source package
pub mod changelog; 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) /// Build a Debian package into a binary (.deb)
pub mod deb; pub mod deb;
/// Reusable Debian format primitives (control/deb822, checksums, versions, /// Reusable Debian format primitives (control/deb822, checksums, versions,
+3 -9
View File
@@ -21,16 +21,14 @@ use std::path::{Path, PathBuf};
use std::time::Duration; use std::time::Duration;
use indicatif::ProgressBar; use indicatif::ProgressBar;
use lazy_static::lazy_static;
use log::debug; use log::debug;
use serde::Deserialize; use serde::Deserialize;
use sha2::{Digest, Sha256}; use sha2::{Digest, Sha256};
use ssh2::{CheckResult, HostKeyType, KnownHostFileKind, KnownHosts, Session}; use ssh2::{CheckResult, HostKeyType, KnownHostFileKind, KnownHosts, Session};
use crate::data::embed_data;
use crate::ui::prompt; use crate::ui::prompt;
const HOST_KEYS_YAML: &str = include_str!("../../data/host_keys.yml");
/// Pinned SSH host key fingerprints, loaded from the bundled /// Pinned SSH host key fingerprints, loaded from the bundled
/// `host_keys.yml` data file (same pattern as `distro_info.yml`): data /// `host_keys.yml` data file (same pattern as `distro_info.yml`): data
/// rather than code, so trust anchors are updatable without touching the /// rather than code, so trust anchors are updatable without touching the
@@ -42,12 +40,8 @@ struct PinnedHostKeys {
fingerprints: HashMap<String, Vec<String>>, fingerprints: HashMap<String, Vec<String>>,
} }
lazy_static! { embed_data! {
// The YAML is include_str!'d at compile time and statically valid; if it static ref PINNED_HOST_KEYS: PinnedHostKeys = "../../data/host_keys.yml"
// 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");
} }
/// Whether `fingerprint` is one of the pinned (published) fingerprints of /// Whether `fingerprint` is one of the pinned (published) fingerprints of
+3 -4
View File
@@ -3,7 +3,7 @@
//! This module provides functionality to read quirks from a YAML file //! This module provides functionality to read quirks from a YAML file
//! and apply them during pull and deb operations. //! and apply them during pull and deb operations.
use lazy_static::lazy_static; use crate::data::embed_data;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::HashMap; use std::collections::HashMap;
@@ -44,9 +44,8 @@ pub struct QuirksConfig {
pub quirks: HashMap<String, PackageQuirks>, pub quirks: HashMap<String, PackageQuirks>,
} }
const QUIRKS_YAML: &str = include_str!("../data/quirks.yml"); embed_data! {
lazy_static! { static ref QUIRKS_DATA: QuirksConfig = "../data/quirks.yml"
static ref QUIRKS_DATA: QuirksConfig = serde_yaml::from_str(QUIRKS_YAML).unwrap();
} }
/// Get quirks for a specific package /// Get quirks for a specific package