hyperdb_bootstrap/release.rs
1// Copyright (c) 2026, Salesforce, Inc. All rights reserved.
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4//! A pinned `hyperd` release descriptor loaded from `hyperd-version.toml`.
5//!
6//! Each `PinnedRelease` records a specific `version` + `build_id` pair
7//! (the two components that make up a Hyper release tag) and the expected
8//! SHA-256 checksums for each platform.
9
10use serde::{Deserialize, Serialize};
11use std::collections::HashMap;
12use std::path::Path;
13
14use crate::platform::Platform;
15use crate::Error;
16
17const BUILTIN_TOML: &str = include_str!("../hyperd-version.toml");
18
19/// A concrete `hyperd` release pinned to a specific version and build, with
20/// optional per-platform SHA-256 checksums.
21///
22/// The "built-in" pin shipped with the crate lives in
23/// `hyperd-bootstrap/hyperd-version.toml` and is available via
24/// [`PinnedRelease::builtin`]. Callers can override it by loading an
25/// external TOML file (see [`PinnedRelease::from_toml_file`]) or by passing
26/// a literal TOML string to [`PinnedRelease::from_toml_str`].
27#[derive(Debug, Clone, Serialize, Deserialize)]
28pub struct PinnedRelease {
29 /// Upstream release version (for example, `"0.0.24457"`).
30 pub version: String,
31 /// Upstream build identifier suffix (for example, `"rc36858b6"`).
32 pub build_id: String,
33 /// Expected SHA-256 digests keyed by platform. Empty strings are treated
34 /// as "no digest" so that partially-filled tables skip verification for
35 /// the missing targets instead of failing outright.
36 #[serde(default)]
37 pub sha256: HashMap<Platform, String>,
38}
39
40impl PinnedRelease {
41 /// Returns the `PinnedRelease` baked into the crate at build time.
42 ///
43 /// # Panics
44 ///
45 /// Panics if the shipped `hyperd-version.toml` fails to parse. This is
46 /// treated as a programmer error — the file is validated by the build
47 /// script and release CI.
48 #[must_use]
49 pub fn builtin() -> Self {
50 toml::from_str(BUILTIN_TOML).expect("baked-in hyperd-version.toml must parse")
51 }
52
53 /// Parses a `PinnedRelease` from an in-memory TOML string.
54 ///
55 /// # Errors
56 ///
57 /// Returns [`Error::TomlParse`] if the text is not valid TOML or the
58 /// document does not match the `PinnedRelease` schema.
59 pub fn from_toml_str(s: &str) -> Result<Self, Error> {
60 toml::from_str(s).map_err(Error::TomlParse)
61 }
62
63 /// Loads a `PinnedRelease` from a TOML file on disk.
64 ///
65 /// # Errors
66 ///
67 /// Returns [`Error::Io`] if the file cannot be read, or
68 /// [`Error::TomlParse`] if the content is not a valid `PinnedRelease`.
69 pub fn from_toml_file(path: &Path) -> Result<Self, Error> {
70 let text = std::fs::read_to_string(path).map_err(|source| {
71 Error::io(format!("reading version file {}", path.display()), source)
72 })?;
73 Self::from_toml_str(&text)
74 }
75
76 /// Returns the expected SHA-256 digest for `platform`, or `None` if the
77 /// release metadata does not pin a digest for that platform. Empty
78 /// strings (common in pre-release metadata) are treated as absent.
79 #[must_use]
80 pub fn sha256_for(&self, platform: Platform) -> Option<&str> {
81 self.sha256
82 .get(&platform)
83 .map(|s| s.trim())
84 .filter(|s| !s.is_empty())
85 }
86
87 /// Returns the full Hyper release tag — `version.build_id` — used in
88 /// download URLs and install directory names.
89 #[must_use]
90 pub fn version_tag(&self) -> String {
91 format!("{}.{}", self.version, self.build_id)
92 }
93}
94
95#[cfg(test)]
96mod tests {
97 use super::*;
98
99 #[test]
100 fn builtin_parses() {
101 let r = PinnedRelease::builtin();
102 assert!(!r.version.is_empty());
103 assert!(!r.build_id.is_empty());
104 }
105
106 #[test]
107 fn empty_sha_is_ignored() {
108 let toml_str = r#"
109version = "0.0.1"
110build_id = "rc1"
111[sha256]
112"macos-arm64" = ""
113"linux-x86_64" = "abc"
114"#;
115 let r = PinnedRelease::from_toml_str(toml_str).unwrap();
116 assert!(r.sha256_for(Platform::MacosArm64).is_none());
117 assert_eq!(r.sha256_for(Platform::LinuxX86_64), Some("abc"));
118 }
119
120 #[test]
121 fn version_tag_format() {
122 let r = PinnedRelease {
123 version: "0.0.24457".to_string(),
124 build_id: "rc36858b6".to_string(),
125 sha256: HashMap::new(),
126 };
127 assert_eq!(r.version_tag(), "0.0.24457.rc36858b6");
128 }
129}