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| Error::Io {
71 context: format!("reading version file {}", path.display()),
72 source,
73 })?;
74 Self::from_toml_str(&text)
75 }
76
77 /// Returns the expected SHA-256 digest for `platform`, or `None` if the
78 /// release metadata does not pin a digest for that platform. Empty
79 /// strings (common in pre-release metadata) are treated as absent.
80 #[must_use]
81 pub fn sha256_for(&self, platform: Platform) -> Option<&str> {
82 self.sha256
83 .get(&platform)
84 .map(|s| s.trim())
85 .filter(|s| !s.is_empty())
86 }
87
88 /// Returns the full Hyper release tag — `version.build_id` — used in
89 /// download URLs and install directory names.
90 #[must_use]
91 pub fn version_tag(&self) -> String {
92 format!("{}.{}", self.version, self.build_id)
93 }
94}
95
96#[cfg(test)]
97mod tests {
98 use super::*;
99
100 #[test]
101 fn builtin_parses() {
102 let r = PinnedRelease::builtin();
103 assert!(!r.version.is_empty());
104 assert!(!r.build_id.is_empty());
105 }
106
107 #[test]
108 fn empty_sha_is_ignored() {
109 let toml_str = r#"
110version = "0.0.1"
111build_id = "rc1"
112[sha256]
113"macos-arm64" = ""
114"linux-x86_64" = "abc"
115"#;
116 let r = PinnedRelease::from_toml_str(toml_str).unwrap();
117 assert!(r.sha256_for(Platform::MacosArm64).is_none());
118 assert_eq!(r.sha256_for(Platform::LinuxX86_64), Some("abc"));
119 }
120
121 #[test]
122 fn version_tag_format() {
123 let r = PinnedRelease {
124 version: "0.0.24457".to_string(),
125 build_id: "rc36858b6".to_string(),
126 sha256: HashMap::new(),
127 };
128 assert_eq!(r.version_tag(), "0.0.24457.rc36858b6");
129 }
130}