From a3a1db81ecd7ed7de4d2c3dc64df7337f2d84efc Mon Sep 17 00:00:00 2001 From: Pedro Henrique Penna Date: Thu, 12 Nov 2026 08:32:02 -0800 Subject: [PATCH 1/3] nvx-rs: delete the error codes and the constructor that nothing makes SL6.8c (#1126) makes ErrorCode and Error's constructors crate-private. Once they are, the warning-denied build of the cli feature, which builds every code of the crate that makes errors, finds three codes and one constructor that nothing makes or calls any more: only the public API and the tests named them. This deletes them first, so that the change of visibility leaves every feature set's build clean: - template-ineligible and template-tombstoned, which the template store reported until it left (SL6.2a, #1121), and sandbox-leased, which the sandbox store reported until it left (SL6.1, #1121). No NVX program reports them, so the CLI prints the same code for every error that it reports. - Error::not_found, which nothing calls. The README's and doc/usage.md's tables of the codes lose the three rows, as the test that compares the README with ErrorCode::ALL, and the test that compares the two tables, require. The public API snapshots lose 4 lines on each platform, the four items, and gain none. Part of #1126 (SL6.8c). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- doc/usage.md | 3 --- nvx-rs/README.md | 3 --- nvx-rs/public-api/linux.txt | 4 ---- nvx-rs/public-api/windows.txt | 4 ---- nvx-rs/src/error.rs | 29 ++--------------------------- 5 files changed, 2 insertions(+), 41 deletions(-) diff --git a/doc/usage.md b/doc/usage.md index c0e223c97..7ea140b68 100644 --- a/doc/usage.md +++ b/doc/usage.md @@ -106,9 +106,6 @@ each code for callers of the `nvx` library. | `foreign-files` | 1 | `deprovision` found files that NVX does not own in the state directory, which the message names, and kept them with the directory. | | `outcome-report-exists` | 1 | A file exists at the `--outcome-report` path. | | `scratch-claimed` | 1 | Another sandbox claims the scratch image. | -| `sandbox-leased` | 1 | The sandbox holds a lease of a template's generation, which allows no other lease, and one launch of its clone, until a release ends it once its VM has exited. | -| `template-ineligible` | 1 | The restore window of a template's generation, which the time contract of its capture sets, excludes the host's time, so no clone may restore it now. | -| `template-tombstoned` | 1 | A tombstone quarantines the template's generation, which takes no new clones. | | `exited` | 1 | OpenVMM exited, as when a start fails, whose message names the category and status of OpenVMM's outcome report when it has one. | | `still-running` | 1 | A stop could not confirm the VM process's exit, so the caller keeps its verified process. | | `exec-refused` | 1 | The guest agent refused the workload, as for a working directory that it cannot use. | diff --git a/nvx-rs/README.md b/nvx-rs/README.md index a6506e192..e508d607e 100644 --- a/nvx-rs/README.md +++ b/nvx-rs/README.md @@ -2405,9 +2405,6 @@ usage errors, which no library error has. | `scratch-claimed` | `TimedOut` | Another sandbox claims the scratch image | | `exec-refused` | `Protocol` | The guest agent refused the workload, as for a working directory that it cannot use | | `endpoint-closed` | `Protocol` | OpenVMM closed the control endpoint, as it does when the VM shuts down | -| `template-ineligible` | `Unavailable` | The restore window of a template's generation, which the time contract of its capture sets, excludes the host's time, so no clone may restore it now: its capture is too old, or the host's clock is behind the capture's | -| `template-tombstoned` | `Unavailable` | A tombstone quarantines the template's generation, which takes no new clones, while the clones that lease it keep their leases | -| `sandbox-leased` | `Other` | The sandbox holds a lease of a template's generation, which allows no other lease, and one launch of its clone, until a release ends it once its VM has exited | ## Guest binaries diff --git a/nvx-rs/public-api/linux.txt b/nvx-rs/public-api/linux.txt index 1b2f72310..21c74ec9e 100644 --- a/nvx-rs/public-api/linux.txt +++ b/nvx-rs/public-api/linux.txt @@ -7,7 +7,6 @@ method nvx::Error::io method nvx::Error::kind method nvx::Error::message method nvx::Error::new -method nvx::Error::not_found method nvx::Error::other method nvx::Error::protocol method nvx::Error::timed_out @@ -33,15 +32,12 @@ variant nvx::ErrorCode::Protocol variant nvx::ErrorCode::RuntimeMismatch variant nvx::ErrorCode::SandboxBusy variant nvx::ErrorCode::SandboxExited -variant nvx::ErrorCode::SandboxLeased variant nvx::ErrorCode::SandboxNotProvisioned variant nvx::ErrorCode::SandboxNotRunning variant nvx::ErrorCode::SandboxProvisioned variant nvx::ErrorCode::SandboxRunning variant nvx::ErrorCode::ScratchClaimed variant nvx::ErrorCode::StillRunning -variant nvx::ErrorCode::TemplateIneligible -variant nvx::ErrorCode::TemplateTombstoned variant nvx::ErrorCode::TimedOut variant nvx::ErrorCode::Unavailable variant nvx::ErrorCode::Unsupported diff --git a/nvx-rs/public-api/windows.txt b/nvx-rs/public-api/windows.txt index 93a33df47..06dfa250b 100644 --- a/nvx-rs/public-api/windows.txt +++ b/nvx-rs/public-api/windows.txt @@ -7,7 +7,6 @@ method nvx::Error::io method nvx::Error::kind method nvx::Error::message method nvx::Error::new -method nvx::Error::not_found method nvx::Error::other method nvx::Error::protocol method nvx::Error::timed_out @@ -33,15 +32,12 @@ variant nvx::ErrorCode::Protocol variant nvx::ErrorCode::RuntimeMismatch variant nvx::ErrorCode::SandboxBusy variant nvx::ErrorCode::SandboxExited -variant nvx::ErrorCode::SandboxLeased variant nvx::ErrorCode::SandboxNotProvisioned variant nvx::ErrorCode::SandboxNotRunning variant nvx::ErrorCode::SandboxProvisioned variant nvx::ErrorCode::SandboxRunning variant nvx::ErrorCode::ScratchClaimed variant nvx::ErrorCode::StillRunning -variant nvx::ErrorCode::TemplateIneligible -variant nvx::ErrorCode::TemplateTombstoned variant nvx::ErrorCode::TimedOut variant nvx::ErrorCode::Unavailable variant nvx::ErrorCode::Unsupported diff --git a/nvx-rs/src/error.rs b/nvx-rs/src/error.rs index a0d5e7500..115fdc624 100644 --- a/nvx-rs/src/error.rs +++ b/nvx-rs/src/error.rs @@ -155,16 +155,6 @@ pub enum ErrorCode { ExecRefused, /// `endpoint-closed`: OpenVMM closed the control endpoint, as it does when the VM shuts down. EndpointClosed, - /// `template-ineligible`: the restore window of a template's generation, which the time - /// contract of its capture sets, excludes the host's time, so no clone may restore it now: its - /// capture is too old, or the host's clock is behind the capture's. - TemplateIneligible, - /// `template-tombstoned`: a tombstone quarantines the template's generation, which takes no - /// new clones, while the clones that lease it keep their leases. - TemplateTombstoned, - /// `sandbox-leased`: the sandbox holds a lease of a template's generation, which allows no - /// other lease, and one launch of its clone, until a release ends it once its VM has exited. - SandboxLeased, } impl ErrorCode { @@ -197,9 +187,6 @@ impl ErrorCode { Self::ScratchClaimed, Self::ExecRefused, Self::EndpointClosed, - Self::TemplateIneligible, - Self::TemplateTombstoned, - Self::SandboxLeased, ]; /// Returns the stable spelling of this code. @@ -232,19 +219,13 @@ impl ErrorCode { Self::ScratchClaimed => "scratch-claimed", Self::ExecRefused => "exec-refused", Self::EndpointClosed => "endpoint-closed", - Self::TemplateIneligible => "template-ineligible", - Self::TemplateTombstoned => "template-tombstoned", - Self::SandboxLeased => "sandbox-leased", } } /// Returns the kind of the errors that have this code. pub const fn kind(self) -> ErrorKind { match self { - Self::Unavailable - | Self::ArtifactMissing - | Self::TemplateIneligible - | Self::TemplateTombstoned => ErrorKind::Unavailable, + Self::Unavailable | Self::ArtifactMissing => ErrorKind::Unavailable, Self::Unsupported | Self::ArgumentUnrecognized | Self::ArgumentMissing @@ -262,8 +243,7 @@ impl ErrorCode { | Self::SandboxRunning | Self::VmmIndeterminate | Self::ForeignFiles - | Self::OutcomeReportExists - | Self::SandboxLeased => ErrorKind::Other, + | Self::OutcomeReportExists => ErrorKind::Other, } } } @@ -337,11 +317,6 @@ impl Error { Self::new(ErrorKind::Io, message) } - /// Creates an [`ErrorKind::NotFound`] error. - pub fn not_found(message: impl Into) -> Self { - Self::new(ErrorKind::NotFound, message) - } - /// Creates an [`ErrorKind::Protocol`] error. pub fn protocol(message: impl Into) -> Self { Self::new(ErrorKind::Protocol, message) From 8484381fa82ae40b0ead9f02a7ec5b0f8acc9644 Mon Sep 17 00:00:00 2001 From: Pedro Henrique Penna Date: Thu, 12 Nov 2026 08:33:20 -0800 Subject: [PATCH 2/3] tests: stop building nvx's errors and listing its codes through its API SL6.8c (#1126) makes Error's constructors and ErrorCode crate-private, and ErrorCode::ALL, which only tests read, goes once the warning-denied builds find it unused. Two tests reached them, which they cannot once those are private, so each keeps its assertions without them: - cold_run_expected, which projects a cold-run fixture onto the public VM contract, built nvx's Unsupported errors for the two fixtures that it cannot project. It now refuses them with errors of its own, as a caller that built nvx's errors must, and still passes on the public contract's own errors. The dry-run and one-shot tests show either only when they report an unexpected refusal. - The error module's tests list the codes themselves, in the order of the README's table, in place of ErrorCode::ALL, for codes_are_unique_lowercase_words_joined_by_hyphens and the_readme_documents_every_code_with_its_kind. They make their errors with Error::new and Error::unavailable, which every feature set builds, in place of Error::io and Error::unsupported, which only the host and guest builds will: errors_keep_their_kind_message_and_cause and codes_determine_kinds check the same kinds, codes, messages and causes. Part of #1126 (SL6.8c). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- nvx-rs/src/error/tests.rs | 45 +++++++++++++++++++++++++++++++------ nvx-rs/tests/cli/openvmm.rs | 17 ++++++++------ 2 files changed, 48 insertions(+), 14 deletions(-) diff --git a/nvx-rs/src/error/tests.rs b/nvx-rs/src/error/tests.rs index a893e72b7..adc381133 100644 --- a/nvx-rs/src/error/tests.rs +++ b/nvx-rs/src/error/tests.rs @@ -1,9 +1,40 @@ use super::*; +/// Every code, in the order of the README's table of codes. +const CODES: [ErrorCode; 27] = [ + ErrorCode::Unavailable, + ErrorCode::Unsupported, + ErrorCode::Exited, + ErrorCode::StillRunning, + ErrorCode::Io, + ErrorCode::NotFound, + ErrorCode::Protocol, + ErrorCode::TimedOut, + ErrorCode::Other, + ErrorCode::ArgumentUnrecognized, + ErrorCode::ArgumentMissing, + ErrorCode::ArgumentInvalid, + ErrorCode::ArtifactMissing, + ErrorCode::SandboxNotProvisioned, + ErrorCode::SandboxProvisioned, + ErrorCode::SandboxNotRunning, + ErrorCode::SandboxRunning, + ErrorCode::SandboxExited, + ErrorCode::SandboxBusy, + ErrorCode::RuntimeMismatch, + ErrorCode::VmmIndeterminate, + ErrorCode::FormatUnsupported, + ErrorCode::ForeignFiles, + ErrorCode::OutcomeReportExists, + ErrorCode::ScratchClaimed, + ErrorCode::ExecRefused, + ErrorCode::EndpointClosed, +]; + #[test] fn errors_keep_their_kind_message_and_cause() { let cause = std::io::Error::other("disk full"); - let error = Error::io("cannot write /state/runtime.json").with_source(cause); + let error = Error::new(ErrorKind::Io, "cannot write /state/runtime.json").with_source(cause); assert_eq!(error.kind(), ErrorKind::Io); assert_eq!(error.code(), ErrorCode::Io); assert_eq!(error.to_string(), "cannot write /state/runtime.json"); @@ -39,7 +70,7 @@ fn codes_determine_kinds() { Error::new(ErrorKind::RuntimeMismatch, "elsewhere").code(), ErrorCode::RuntimeMismatch ); - assert_eq!(Error::unsupported("no").code(), ErrorCode::Unsupported); + assert_eq!(Error::unavailable("no").code(), ErrorCode::Unavailable); assert_eq!( ErrorCode::SandboxNotRunning.to_string(), "sandbox-not-running" @@ -69,7 +100,7 @@ fn every_kind_has_a_code_of_its_own_spelling() { #[test] fn codes_are_unique_lowercase_words_joined_by_hyphens() { let mut seen = std::collections::HashSet::new(); - for code in ErrorCode::ALL { + for code in CODES { let spelling = code.as_str(); assert!(seen.insert(spelling), "{spelling} repeats"); assert!( @@ -79,17 +110,17 @@ fn codes_are_unique_lowercase_words_joined_by_hyphens() { "{spelling}" ); // A finer code is never spelled as a kind, whose code it would shadow. - if ErrorCode::from(code.kind()) != *code { + if ErrorCode::from(code.kind()) != code { assert_ne!(spelling, code.kind().as_str()); } } - assert_eq!(seen.len(), ErrorCode::ALL.len()); + assert_eq!(seen.len(), CODES.len()); } #[test] fn the_readme_documents_every_code_with_its_kind() { // The table of the README's "Error codes" section lists each code and its kind, in the - // order of `ErrorCode::ALL`. + // order of `CODES`. let readme = include_str!("../../README.md"); let documented: Vec<(String, String)> = readme .lines() @@ -103,7 +134,7 @@ fn the_readme_documents_every_code_with_its_kind() { Some((code.to_owned(), kind.to_owned())) }) .collect(); - let expected: Vec<(String, String)> = ErrorCode::ALL + let expected: Vec<(String, String)> = CODES .iter() .map(|code| (code.as_str().to_owned(), format!("{:?}", code.kind()))) .collect(); diff --git a/nvx-rs/tests/cli/openvmm.rs b/nvx-rs/tests/cli/openvmm.rs index c38db9c97..249f8d283 100644 --- a/nvx-rs/tests/cli/openvmm.rs +++ b/nvx-rs/tests/cli/openvmm.rs @@ -353,7 +353,11 @@ impl Options { /// Projects an old cold-run fixture onto the checked public VM contract, independently of /// the CLI's parser, planner, and private token renderer. Restore and sandbox fixtures stay /// old. - pub(crate) fn cold_run_expected(&self, root: &Root, mode: &str) -> nvx::Result { + pub(crate) fn cold_run_expected( + &self, + root: &Root, + mode: &str, + ) -> Result> { use nvx::activation::{MachineIdMode, View}; use nvx::host::config::{Boot, ConsoleAttachment, NetworkConfig, RootConfig, VmConfig}; use nvx::host::filesystem::Owner; @@ -433,9 +437,10 @@ impl Options { for value in old.values(option) { let path = PathBuf::from(value); if shares.is_empty() || (aggregate && !path.is_absolute()) { - return Err(nvx::Error::unsupported( - "boot share policies need a mapped path and absolute aggregate paths", - )); + return Err( + "boot share policies need a mapped path and absolute aggregate paths" + .into(), + ); } policies[index].push(if path.is_absolute() { path @@ -482,9 +487,7 @@ impl Options { .into_iter() .any(|option| !old.values(option).is_empty()) { - return Err(nvx::Error::unsupported( - "network policy options require --net", - )); + return Err("network policy options require --net".into()); } let network = if let Some(net) = old.values("--net").first() { let access = |option, default| match old.values(option).first().map(String::as_str) { From b92df2b8db15cbf3e274124aa57d7bd42a611b80 Mon Sep 17 00:00:00 2001 From: Pedro Henrique Penna Date: Thu, 12 Nov 2026 08:34:12 -0800 Subject: [PATCH 3/3] nvx-rs: make the error codes and Error's constructors private Callers act on nvx's failures by an error's kind, message and source, but ErrorCode, Error::code and Error's constructors were public: the codes, which serve the CLI's output, were part of the library's API, and any caller could fabricate nvx's errors. They become crate-private where they are, so that nvx alone makes its errors, and its public error is Error, ErrorKind and Result: - ErrorCode, with as_str and kind, and Error::code become pub(crate), and the crate root re-exports ErrorCode to the crate only, for the host and the CLI, which name it there. ErrorCode loses #[non_exhaustive], which only bound other crates, and ErrorCode::ALL, which only tests read. - Error::new, its constructor of each kind, and with_source become pub(crate), gated by the features of their callers: unsupported and io build with the host or the guest, exited, protocol, timed_out and other with the host, and new, unavailable and with_source with every feature set. - The CLI keeps its codes, and prints each error's code with the same messages and exit statuses. Most finer codes come from the CLI alone, and the CLI reads Error::code, as nvx-init and tests do, so the builds without the cli feature allow both to be unused. - Error keeps kind, message, into_parts, its display and its source, and ErrorKind its variants, as_str and its display. Their docs, and those of SnapshotKey::from_bytes and SnapshotManifest::parse, name kinds and no codes, and the docs of the CLI's hidden module no longer link the private ErrorCode. The README's Errors section describes the error by its kind, which only nvx makes, and its Error codes section the codes that the CLI prints, which the library keeps private. doc/usage.md, and a comment of the test that compares the two tables, follow. The public API snapshots lose 42 lines on each platform, ErrorCode with its variants and methods, and Error's constructors and code, and gain none. Part of #1126 (SL6.8c). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- doc/usage.md | 2 +- nvx-rs/README.md | 38 ++++++----- nvx-rs/public-api/linux.txt | 41 ------------ nvx-rs/public-api/windows.txt | 41 ------------ nvx-rs/src/error.rs | 99 +++++++++++----------------- nvx-rs/src/host/cli/mod.rs | 2 +- nvx-rs/src/host/snapshot/key.rs | 5 +- nvx-rs/src/host/snapshot/manifest.rs | 10 ++- nvx-rs/src/lib.rs | 4 +- scripts/test_nvx_tools.py | 2 +- 10 files changed, 72 insertions(+), 172 deletions(-) diff --git a/doc/usage.md b/doc/usage.md index 7ea140b68..275e23a42 100644 --- a/doc/usage.md +++ b/doc/usage.md @@ -84,7 +84,7 @@ the command. The usage errors that `nvx.py` itself reports, as `argparse` does, carry no code, and neither does an interruption by SIGINT or, outside Windows, SIGTERM, which prints `Interrupted` and exits with status 130. The [`nvx-rs` README](../nvx-rs/README.md#error-codes) gives the `ErrorKind` of -each code for callers of the `nvx` library. +each code, which callers of the `nvx` library see in its place. | Code | Status | Meaning | | --- | --- | --- | diff --git a/nvx-rs/README.md b/nvx-rs/README.md index e508d607e..9d1db5cb0 100644 --- a/nvx-rs/README.md +++ b/nvx-rs/README.md @@ -42,7 +42,7 @@ nvx-rs/ │ ├── uuid.rs UUIDs in their canonical text and in GPT's layout; no dependencies, shared │ │ by the host and the guest │ ├── hypervisor.rs -│ ├── error.rs nvx::Error, its ErrorKind, and its stable ErrorCode +│ ├── error.rs nvx::Error, its ErrorKind, and the stable codes that the CLI prints │ ├── host/ feature `host` (default): the runtime that every launch shares, VM │ │ directories and configurations, artifacts, network and egress policy files, │ │ filesystem, NVXS connection, session, process, platform, templates' @@ -2342,9 +2342,11 @@ fn main() -> Result<(), Box> { ## Errors -Every fallible operation returns `nvx::Error`: a message that callers can show -as is, an `ErrorCode`, which determines the error's `ErrorKind`, and an -optional cause. +Every fallible operation returns `nvx::Error`, which only `nvx` makes: a +message that callers can show as is, an `ErrorKind`, and an optional cause. +Callers tell errors apart by their kinds, and by stable fragments of their +messages, such as the names of options, fields, files, and processes, since the +wording of a message may change. | `ErrorKind` | Meaning | | --- | --- | @@ -2361,20 +2363,20 @@ optional cause. ### Error codes -An `ErrorCode` is finer than an `ErrorKind`: callers tell errors apart by their -codes, and by stable fragments of their messages, such as the names of options, -fields, files, and processes, since the wording of a message may change. The -[CLI](#the-nvx-rs-cli) prints the code of each error that it reports. - -`ErrorCode::as_str` spells a code in lowercase ASCII words joined by hyphens. A -failure that a caller can act on has a code of its own, which names the subject -of the failure and then its condition, such as `sandbox-not-running`. Any other -failure has the code of its kind, which is spelled as the kind, such as `io`, -and which an `ErrorKind` converts into. A code keeps its spelling, its kind, and -its meaning, and is never reused for another failure: a failure that callers -come to act on gets a new code, so a caller treats a code that it does not know -as a failure of the operation. The `argument-*` codes are those of the CLI's -usage errors, which no library error has. +The [CLI](#the-nvx-rs-cli) prints a stable code with each error that it +reports, finer than the error's `ErrorKind`, by which callers of the CLI tell +errors apart. The codes are private to `nvx`: callers of the library see the +kind of each code, which the table gives. + +A code is spelled in lowercase ASCII words joined by hyphens. A failure that a +caller can act on has a code of its own, which names the subject of the failure +and then its condition, such as `sandbox-not-running`. Any other failure has +the code of its kind, which is spelled as the kind, such as `io`. A code keeps +its spelling, its kind, and its meaning, and is never reused for another +failure: a failure that callers come to act on gets a new code, so a caller +treats a code that it does not know as a failure of the operation. The +`argument-*` codes are those of the CLI's usage errors, which no library error +has. | Code | `ErrorKind` | Meaning | | --- | --- | --- | diff --git a/nvx-rs/public-api/linux.txt b/nvx-rs/public-api/linux.txt index 21c74ec9e..45ba2cbfb 100644 --- a/nvx-rs/public-api/linux.txt +++ b/nvx-rs/public-api/linux.txt @@ -1,49 +1,8 @@ mod nvx struct nvx::Error -method nvx::Error::code -method nvx::Error::exited method nvx::Error::into_parts -method nvx::Error::io method nvx::Error::kind method nvx::Error::message -method nvx::Error::new -method nvx::Error::other -method nvx::Error::protocol -method nvx::Error::timed_out -method nvx::Error::unavailable -method nvx::Error::unsupported -method nvx::Error::with_source -enum nvx::ErrorCode -const nvx::ErrorCode::ALL -variant nvx::ErrorCode::ArgumentInvalid -variant nvx::ErrorCode::ArgumentMissing -variant nvx::ErrorCode::ArgumentUnrecognized -variant nvx::ErrorCode::ArtifactMissing -variant nvx::ErrorCode::EndpointClosed -variant nvx::ErrorCode::ExecRefused -variant nvx::ErrorCode::Exited -variant nvx::ErrorCode::ForeignFiles -variant nvx::ErrorCode::FormatUnsupported -variant nvx::ErrorCode::Io -variant nvx::ErrorCode::NotFound -variant nvx::ErrorCode::Other -variant nvx::ErrorCode::OutcomeReportExists -variant nvx::ErrorCode::Protocol -variant nvx::ErrorCode::RuntimeMismatch -variant nvx::ErrorCode::SandboxBusy -variant nvx::ErrorCode::SandboxExited -variant nvx::ErrorCode::SandboxNotProvisioned -variant nvx::ErrorCode::SandboxNotRunning -variant nvx::ErrorCode::SandboxProvisioned -variant nvx::ErrorCode::SandboxRunning -variant nvx::ErrorCode::ScratchClaimed -variant nvx::ErrorCode::StillRunning -variant nvx::ErrorCode::TimedOut -variant nvx::ErrorCode::Unavailable -variant nvx::ErrorCode::Unsupported -variant nvx::ErrorCode::VmmIndeterminate -method nvx::ErrorCode::as_str -method nvx::ErrorCode::kind enum nvx::ErrorKind variant nvx::ErrorKind::Exited variant nvx::ErrorKind::Io diff --git a/nvx-rs/public-api/windows.txt b/nvx-rs/public-api/windows.txt index 06dfa250b..1b9b6199a 100644 --- a/nvx-rs/public-api/windows.txt +++ b/nvx-rs/public-api/windows.txt @@ -1,49 +1,8 @@ mod nvx struct nvx::Error -method nvx::Error::code -method nvx::Error::exited method nvx::Error::into_parts -method nvx::Error::io method nvx::Error::kind method nvx::Error::message -method nvx::Error::new -method nvx::Error::other -method nvx::Error::protocol -method nvx::Error::timed_out -method nvx::Error::unavailable -method nvx::Error::unsupported -method nvx::Error::with_source -enum nvx::ErrorCode -const nvx::ErrorCode::ALL -variant nvx::ErrorCode::ArgumentInvalid -variant nvx::ErrorCode::ArgumentMissing -variant nvx::ErrorCode::ArgumentUnrecognized -variant nvx::ErrorCode::ArtifactMissing -variant nvx::ErrorCode::EndpointClosed -variant nvx::ErrorCode::ExecRefused -variant nvx::ErrorCode::Exited -variant nvx::ErrorCode::ForeignFiles -variant nvx::ErrorCode::FormatUnsupported -variant nvx::ErrorCode::Io -variant nvx::ErrorCode::NotFound -variant nvx::ErrorCode::Other -variant nvx::ErrorCode::OutcomeReportExists -variant nvx::ErrorCode::Protocol -variant nvx::ErrorCode::RuntimeMismatch -variant nvx::ErrorCode::SandboxBusy -variant nvx::ErrorCode::SandboxExited -variant nvx::ErrorCode::SandboxNotProvisioned -variant nvx::ErrorCode::SandboxNotRunning -variant nvx::ErrorCode::SandboxProvisioned -variant nvx::ErrorCode::SandboxRunning -variant nvx::ErrorCode::ScratchClaimed -variant nvx::ErrorCode::StillRunning -variant nvx::ErrorCode::TimedOut -variant nvx::ErrorCode::Unavailable -variant nvx::ErrorCode::Unsupported -variant nvx::ErrorCode::VmmIndeterminate -method nvx::ErrorCode::as_str -method nvx::ErrorCode::kind enum nvx::ErrorKind variant nvx::ErrorKind::Exited variant nvx::ErrorKind::Io diff --git a/nvx-rs/src/error.rs b/nvx-rs/src/error.rs index 115fdc624..fa8877dfb 100644 --- a/nvx-rs/src/error.rs +++ b/nvx-rs/src/error.rs @@ -5,7 +5,7 @@ use std::fmt; /// Result type of NVX operations. pub type Result = std::result::Result; -/// Classification of an [`Error`], which its [`ErrorCode`] determines. +/// Classification of an [`Error`]. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[non_exhaustive] pub enum ErrorKind { @@ -63,7 +63,9 @@ impl fmt::Display for ErrorKind { } } -/// Stable, machine-readable code of an [`Error`], finer than its [`ErrorKind`]. +/// Stable, machine-readable code of an [`Error`], finer than its [`ErrorKind`], which the `nvx-rs` +/// CLI prints with each error that it reports. Codes are private to the crate: callers of the +/// library tell errors apart by their kinds. /// /// Each code belongs to one kind, which [`kind`](Self::kind) returns. A failure that a caller can /// act on has a code of its own, such as `sandbox-not-running`; any other failure has its kind's @@ -73,14 +75,16 @@ impl fmt::Display for ErrorKind { /// a kind, or the subject of the failure followed by its condition, such as `artifact-missing`. /// Codes are stable: a code keeps its spelling, its kind, and its meaning, and is never reused for /// another failure, so a new failure that a caller can act on gets a new code. Messages are not -/// stable, so callers tell errors apart by their codes, and by stable fragments of their messages, -/// such as the names of options and fields. +/// stable, so the CLI's callers tell errors apart by their codes, and by stable fragments of their +/// messages, such as the names of options and fields. /// -/// The `nvx-rs` CLI prints the code of every error that it reports, including the `argument-*` -/// codes of its usage errors, which no library error has. +/// The CLI's usage errors have the `argument-*` codes, which no library error has. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -#[non_exhaustive] -pub enum ErrorCode { +#[cfg_attr( + not(feature = "cli"), + allow(dead_code, reason = "the CLI makes most of the finer codes") +)] +pub(crate) enum ErrorCode { /// `unavailable`: a runtime dependency is missing or unusable, other than as a finer code /// says, such as install metadata, the hypervisor, a release, or a host that NVX does not /// support. @@ -158,39 +162,8 @@ pub enum ErrorCode { } impl ErrorCode { - /// Every code of this release. - pub const ALL: &'static [ErrorCode] = &[ - Self::Unavailable, - Self::Unsupported, - Self::Exited, - Self::StillRunning, - Self::Io, - Self::NotFound, - Self::Protocol, - Self::TimedOut, - Self::Other, - Self::ArgumentUnrecognized, - Self::ArgumentMissing, - Self::ArgumentInvalid, - Self::ArtifactMissing, - Self::SandboxNotProvisioned, - Self::SandboxProvisioned, - Self::SandboxNotRunning, - Self::SandboxRunning, - Self::SandboxExited, - Self::SandboxBusy, - Self::RuntimeMismatch, - Self::VmmIndeterminate, - Self::FormatUnsupported, - Self::ForeignFiles, - Self::OutcomeReportExists, - Self::ScratchClaimed, - Self::ExecRefused, - Self::EndpointClosed, - ]; - /// Returns the stable spelling of this code. - pub const fn as_str(self) -> &'static str { + pub(crate) const fn as_str(self) -> &'static str { match self { Self::Unavailable => ErrorKind::Unavailable.as_str(), Self::Unsupported => ErrorKind::Unsupported.as_str(), @@ -223,7 +196,7 @@ impl ErrorCode { } /// Returns the kind of the errors that have this code. - pub const fn kind(self) -> ErrorKind { + pub(crate) const fn kind(self) -> ErrorKind { match self { Self::Unavailable | Self::ArtifactMissing => ErrorKind::Unavailable, Self::Unsupported @@ -272,14 +245,13 @@ impl From for ErrorCode { } } -/// Error of an NVX operation: its [`ErrorCode`], which determines its [`ErrorKind`], a -/// description, and an optional cause. +/// Error of an NVX operation: its [`ErrorKind`], a description, and an optional cause. /// /// The description is a complete sentence fragment that callers can show as is, so callers map -/// errors onto their own classifications by [`kind`](Self::kind) or [`code`](Self::code) alone. +/// errors onto their own classifications by [`kind`](Self::kind) alone. #[derive(Debug)] pub struct Error { - /// Stable code that callers use when telling the failure apart. + /// Stable code, which determines the error's kind, and which the CLI prints. code: ErrorCode, /// Complete, user-facing description without a trailing period. message: String, @@ -289,7 +261,7 @@ pub struct Error { impl Error { /// Creates an error with the given code, or the code of the given kind, and description. - pub fn new(code: impl Into, message: impl Into) -> Self { + pub(crate) fn new(code: impl Into, message: impl Into) -> Self { Self { code: code.into(), message: message.into(), @@ -298,43 +270,49 @@ impl Error { } /// Creates an [`ErrorKind::Unavailable`] error. - pub fn unavailable(message: impl Into) -> Self { + pub(crate) fn unavailable(message: impl Into) -> Self { Self::new(ErrorKind::Unavailable, message) } /// Creates an [`ErrorKind::Unsupported`] error. - pub fn unsupported(message: impl Into) -> Self { + #[cfg(any(feature = "host", all(feature = "guest", target_os = "linux")))] + pub(crate) fn unsupported(message: impl Into) -> Self { Self::new(ErrorKind::Unsupported, message) } /// Creates an [`ErrorKind::Exited`] error. - pub fn exited(message: impl Into) -> Self { + #[cfg(feature = "host")] + pub(crate) fn exited(message: impl Into) -> Self { Self::new(ErrorKind::Exited, message) } /// Creates an [`ErrorKind::Io`] error. - pub fn io(message: impl Into) -> Self { + #[cfg(any(feature = "host", all(feature = "guest", target_os = "linux")))] + pub(crate) fn io(message: impl Into) -> Self { Self::new(ErrorKind::Io, message) } /// Creates an [`ErrorKind::Protocol`] error. - pub fn protocol(message: impl Into) -> Self { + #[cfg(feature = "host")] + pub(crate) fn protocol(message: impl Into) -> Self { Self::new(ErrorKind::Protocol, message) } /// Creates an [`ErrorKind::TimedOut`] error. - pub fn timed_out(message: impl Into) -> Self { + #[cfg(feature = "host")] + pub(crate) fn timed_out(message: impl Into) -> Self { Self::new(ErrorKind::TimedOut, message) } /// Creates an [`ErrorKind::Other`] error. - pub fn other(message: impl Into) -> Self { + #[cfg(feature = "host")] + pub(crate) fn other(message: impl Into) -> Self { Self::new(ErrorKind::Other, message) } /// Attaches an underlying cause to this error. #[must_use] - pub fn with_source( + pub(crate) fn with_source( mut self, source: impl Into>, ) -> Self { @@ -342,13 +320,17 @@ impl Error { self } - /// Returns the classification of this error, which its code determines. + /// Returns the classification of this error. pub fn kind(&self) -> ErrorKind { self.code.kind() } - /// Returns the stable code of this error. - pub fn code(&self) -> ErrorCode { + /// Returns the stable code of this error, which the CLI prints. + #[cfg_attr( + not(feature = "cli"), + allow(dead_code, reason = "the CLI prints the codes") + )] + pub(crate) fn code(&self) -> ErrorCode { self.code } @@ -358,8 +340,7 @@ impl Error { } /// Splits the error into its kind, description, and cause, so that a caller can rebuild it - /// as an error of its own without adding a level to the chain of causes. A caller that keeps - /// the code reads it with [`code`](Self::code) first. + /// as an error of its own without adding a level to the chain of causes. pub fn into_parts( self, ) -> ( diff --git a/nvx-rs/src/host/cli/mod.rs b/nvx-rs/src/host/cli/mod.rs index 01d9adf3d..301a603b4 100644 --- a/nvx-rs/src/host/cli/mod.rs +++ b/nvx-rs/src/host/cli/mod.rs @@ -31,7 +31,7 @@ //! is `0`. //! //! Errors print `error[CODE]: MESSAGE` to standard error and exit with status 1, as `nvx.py` -//! reports a `ScriptError`, with the stable [`ErrorCode`] of the failure. Usage errors print the +//! reports a `ScriptError`, with the stable `ErrorCode` of the failure. Usage errors print the //! one-line usage of the parser and `PROG: error[CODE]: MESSAGE`, where `PROG` is the parser's //! name, such as `nvx-rs run`, with one of the `argument-*` codes, and exit with status 2, as //! `argparse` does. Help prints the usage and every option of the parser, and exits with status 0. diff --git a/nvx-rs/src/host/snapshot/key.rs b/nvx-rs/src/host/snapshot/key.rs index bfc3d5406..e69402ed0 100644 --- a/nvx-rs/src/host/snapshot/key.rs +++ b/nvx-rs/src/host/snapshot/key.rs @@ -662,9 +662,8 @@ impl SnapshotKey { /// The bytes must be the key's one encoding, of version [`VERSION`](Self::VERSION), with no /// value of another form than a key derived from a manifest has, and no byte left over. /// - /// Fails with [`ErrorCode::FormatUnsupported`](crate::ErrorCode::FormatUnsupported) for a key - /// of another version, and with [`ErrorKind::Io`](crate::ErrorKind::Io) for bytes that are - /// not such a key. + /// Fails with [`ErrorKind::Io`](crate::ErrorKind::Io) for a key of another version, and for + /// bytes that are not such a key. pub fn from_bytes(bytes: &[u8]) -> Result { if bytes.len() > Self::MAX_LEN { return Err(Error::io(format!( diff --git a/nvx-rs/src/host/snapshot/manifest.rs b/nvx-rs/src/host/snapshot/manifest.rs index f8bcf392a..cb75fab9f 100644 --- a/nvx-rs/src/host/snapshot/manifest.rs +++ b/nvx-rs/src/host/snapshot/manifest.rs @@ -670,12 +670,10 @@ impl SnapshotManifest { /// Parses and checks the bytes of a template's manifest. /// - /// Fails with [`ErrorCode::FormatUnsupported`] for a - /// manifest of another version of the snapshot format, saved-state schema, machine ABI, boot - /// layout, or time ABI than this build reads, and with - /// [`ErrorKind::Io`](crate::ErrorKind::Io) for more than [`MAX_MANIFEST_LEN`] bytes, bytes - /// that are not such a manifest, and a manifest that is not a template's, as - /// [`SnapshotManifest`] describes. + /// Fails with [`ErrorKind::Io`](crate::ErrorKind::Io) for a manifest of another version of the + /// snapshot format, saved-state schema, machine ABI, boot layout, or time ABI than this build + /// reads, for more than [`MAX_MANIFEST_LEN`] bytes, bytes that are not such a manifest, and a + /// manifest that is not a template's, as [`SnapshotManifest`] describes. pub fn parse(bytes: &[u8]) -> Result { Self::parse_described(bytes, "the snapshot manifest") } diff --git a/nvx-rs/src/lib.rs b/nvx-rs/src/lib.rs index 468d1a576..645c407ac 100644 --- a/nvx-rs/src/lib.rs +++ b/nvx-rs/src/lib.rs @@ -155,7 +155,9 @@ pub mod testing; #[cfg(test)] mod tests; -pub use error::{Error, ErrorCode, ErrorKind, Result}; +#[cfg(feature = "host")] +pub(crate) use error::ErrorCode; +pub use error::{Error, ErrorKind, Result}; #[cfg(feature = "host")] pub use host::filesystem::guest_path; pub use hypervisor::Hypervisor; diff --git a/scripts/test_nvx_tools.py b/scripts/test_nvx_tools.py index 581add13a..3b054e637 100644 --- a/scripts/test_nvx_tools.py +++ b/scripts/test_nvx_tools.py @@ -2354,7 +2354,7 @@ def test_reported_errors_are_the_last_that_nvx_rs_printed(self): self.assertIsNone(nvx_rs.reported_error(stderr)) def test_usage_documents_every_error_code_of_nvx_rs(self): - # nvx-rs/README.md lists every code of nvx::ErrorCode, which a test of + # nvx-rs/README.md lists every code that nvx-rs prints, which a test of # nvx-rs checks, and doc/usage.md lists the same codes with the status # with which nvx-rs exits: 2 for usage errors and 1 for any other. def table(path: Path) -> list[tuple[str, str]]: