refactor: Switch to using miette for errors instead of anyhow (#198)

Switch to a better error crate that will allow setting help texts for
any error we want.
This commit is contained in:
Gerald Pinder 2024-07-05 21:55:43 -04:00 committed by GitHub
parent 784be9869a
commit 065fa193e3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 364 additions and 143 deletions

146
Cargo.lock generated
View file

@ -2,6 +2,15 @@
# It is not intended for manual editing.
version = 3
[[package]]
name = "addr2line"
version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678"
dependencies = [
"gimli",
]
[[package]]
name = "adler"
version = "1.0.2"
@ -148,6 +157,30 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
[[package]]
name = "backtrace"
version = "0.3.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a"
dependencies = [
"addr2line",
"cc",
"cfg-if",
"libc",
"miniz_oxide",
"object",
"rustc-demangle",
]
[[package]]
name = "backtrace-ext"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "537beee3be4a18fb023b570f80e3ae28003db9167a751266b259926e25539d50"
dependencies = [
"backtrace",
]
[[package]]
name = "base64"
version = "0.21.7"
@ -212,7 +245,6 @@ dependencies = [
name = "blue-build"
version = "0.8.11"
dependencies = [
"anyhow",
"blue-build-recipe",
"blue-build-template",
"blue-build-utils",
@ -227,6 +259,7 @@ dependencies = [
"indicatif",
"lenient_semver",
"log",
"miette",
"once_cell",
"open",
"os_info",
@ -249,12 +282,12 @@ dependencies = [
name = "blue-build-recipe"
version = "0.8.11"
dependencies = [
"anyhow",
"blue-build-utils",
"chrono",
"colored",
"indexmap 2.2.6",
"log",
"miette",
"serde",
"serde_json",
"serde_yaml 0.9.34+deprecated",
@ -293,6 +326,7 @@ dependencies = [
"indicatif-log-bridge",
"log",
"log4rs",
"miette",
"nix",
"nu-ansi-term",
"once_cell",
@ -753,6 +787,12 @@ dependencies = [
"wasi",
]
[[package]]
name = "gimli"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd"
[[package]]
name = "git2"
version = "0.18.3"
@ -924,6 +964,12 @@ dependencies = [
"once_cell",
]
[[package]]
name = "is_ci"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45"
[[package]]
name = "is_debug"
version = "1.0.1"
@ -1118,6 +1164,37 @@ version = "2.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d"
[[package]]
name = "miette"
version = "7.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4edc8853320c2a0dab800fbda86253c8938f6ea88510dc92c5f1ed20e794afc1"
dependencies = [
"backtrace",
"backtrace-ext",
"cfg-if",
"miette-derive",
"owo-colors",
"supports-color",
"supports-hyperlinks",
"supports-unicode",
"terminal_size",
"textwrap 0.16.1",
"thiserror",
"unicode-width",
]
[[package]]
name = "miette-derive"
version = "7.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcf09caffaac8068c346b6df2a7fc27a177fd20b39421a39ce0a211bde679a6c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.66",
]
[[package]]
name = "mime"
version = "0.3.17"
@ -1234,6 +1311,15 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef"
[[package]]
name = "object"
version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "081b846d1d56ddfc18fdf1a922e4f6e07a11768ea1b92dec44e42b72712ccfce"
dependencies = [
"memchr",
]
[[package]]
name = "once_cell"
version = "1.19.0"
@ -1309,6 +1395,12 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "owo-colors"
version = "4.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "caff54706df99d2a78a5a4e3455ff45448d81ef1bb63c22cd14052ca0e993a3f"
[[package]]
name = "parking_lot"
version = "0.12.3"
@ -1594,10 +1686,16 @@ dependencies = [
"crossterm",
"once_cell",
"termion",
"textwrap",
"textwrap 0.15.2",
"unicode-segmentation",
]
[[package]]
name = "rustc-demangle"
version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
[[package]]
name = "rustix"
version = "0.38.34"
@ -1799,6 +1897,27 @@ version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
[[package]]
name = "supports-color"
version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9829b314621dfc575df4e409e79f9d6a66a3bd707ab73f23cb4aa3a854ac854f"
dependencies = [
"is_ci",
]
[[package]]
name = "supports-hyperlinks"
version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c0a1e5168041f5f3ff68ff7d95dcb9c8749df29f6e7e89ada40dd4c9de404ee"
[[package]]
name = "supports-unicode"
version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7401a30af6cb5818bb64852270bb722533397edcfc7344954a38f420819ece2"
[[package]]
name = "syn"
version = "1.0.109"
@ -1865,6 +1984,16 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "terminal_size"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7"
dependencies = [
"rustix",
"windows-sys 0.48.0",
]
[[package]]
name = "termion"
version = "1.5.6"
@ -1888,6 +2017,17 @@ dependencies = [
"unicode-width",
]
[[package]]
name = "textwrap"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9"
dependencies = [
"smawk",
"unicode-linebreak",
"unicode-width",
]
[[package]]
name = "thiserror"
version = "1.0.61"

View file

@ -10,7 +10,6 @@ categories = ["command-line-utilities"]
version = "0.8.11"
[workspace.dependencies]
anyhow = "1"
chrono = "0.4"
clap = "4"
colored = "2"
@ -19,6 +18,7 @@ indexmap = { version = "2", features = ["serde"] }
indicatif = { version = "0.17", features = ["improved_unicode"] }
indicatif-log-bridge = "0.2"
log = "0.4"
miette = "7"
once_cell = "1"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
@ -73,14 +73,13 @@ shadow-rs = "0.26"
urlencoding = "2"
users = "0.11"
# Workspace dependencies
anyhow.workspace = true
chrono.workspace = true
clap = { workspace = true, features = ["derive", "cargo", "unicode", "env"] }
colored.workspace = true
indexmap.workspace = true
indicatif.workspace = true
log.workspace = true
miette = { workspace = true, features = ["fancy"] }
once_cell.workspace = true
serde.workspace = true
serde_json.workspace = true

View file

@ -11,10 +11,10 @@ license.workspace = true
[dependencies]
blue-build-utils = { version = "=0.8.11", path = "../utils" }
anyhow.workspace = true
chrono.workspace = true
colored.workspace = true
log.workspace = true
miette.workspace = true
indexmap.workspace = true
serde.workspace = true
serde_yaml.workspace = true

View file

@ -1,10 +1,10 @@
use std::{borrow::Cow, path::PathBuf, process};
use anyhow::{bail, Result};
use blue_build_utils::syntax_highlighting::highlight_ser;
use colored::Colorize;
use indexmap::IndexMap;
use log::{error, trace, warn};
use miette::{bail, Result};
use serde::{Deserialize, Serialize};
use serde_yaml::Value;
use typed_builder::TypedBuilder;

View file

@ -1,8 +1,8 @@
use std::{borrow::Cow, collections::HashSet, fs, path::Path};
use anyhow::{Context, Result};
use blue_build_utils::constants::{CONFIG_PATH, RECIPE_PATH};
use log::{trace, warn};
use miette::{Context, IntoDiagnostic, Result};
use serde::{Deserialize, Serialize};
use typed_builder::TypedBuilder;
@ -32,12 +32,14 @@ impl ModuleExt<'_> {
};
let file = fs::read_to_string(&file_path)
.context(format!("Failed to open {}", file_path.display()))?;
.into_diagnostic()
.with_context(|| format!("Failed to open {}", file_path.display()))?;
serde_yaml::from_str::<Self>(&file).map_or_else(
|_| -> Result<Self> {
let module = serde_yaml::from_str::<Module>(&file)
.map_err(blue_build_utils::serde_yaml_err(&file))?;
.map_err(blue_build_utils::serde_yaml_err(&file))
.into_diagnostic()?;
Ok(Self::builder().modules(vec![module]).build())
},
Ok,

View file

@ -1,6 +1,5 @@
use std::{borrow::Cow, env, fs, path::Path};
use anyhow::{Context, Result};
use blue_build_utils::constants::{
CI_COMMIT_REF_NAME, CI_COMMIT_SHORT_SHA, CI_DEFAULT_BRANCH, CI_MERGE_REQUEST_IID,
CI_PIPELINE_SOURCE, GITHUB_EVENT_NAME, GITHUB_REF_NAME, GITHUB_SHA, PR_EVENT_NUMBER,
@ -8,6 +7,7 @@ use blue_build_utils::constants::{
use chrono::Local;
use indexmap::IndexMap;
use log::{debug, trace, warn};
use miette::{Context, IntoDiagnostic, Result};
use serde::{Deserialize, Serialize};
use serde_yaml::Value;
use typed_builder::TypedBuilder;
@ -204,16 +204,20 @@ impl<'a> Recipe<'a> {
let file_path = if Path::new(path.as_ref()).is_absolute() {
path.as_ref().to_path_buf()
} else {
std::env::current_dir()?.join(path.as_ref())
std::env::current_dir()
.into_diagnostic()?
.join(path.as_ref())
};
let file = fs::read_to_string(&file_path)
.context(format!("Failed to read {}", file_path.display()))?;
.into_diagnostic()
.with_context(|| format!("Failed to read {}", file_path.display()))?;
debug!("Recipe contents: {file}");
let mut recipe = serde_yaml::from_str::<Recipe>(&file)
.map_err(blue_build_utils::serde_yaml_err(&file))?;
.map_err(blue_build_utils::serde_yaml_err(&file))
.into_diagnostic()?;
recipe.modules_ext.modules = Module::get_modules(&recipe.modules_ext.modules, None)?.into();

View file

@ -1,8 +1,8 @@
use std::{borrow::Cow, path::PathBuf};
use anyhow::{bail, Result};
use blue_build_utils::syntax_highlighting::highlight_ser;
use colored::Colorize;
use miette::{bail, Result};
use serde::{Deserialize, Serialize};
use typed_builder::TypedBuilder;

View file

@ -1,8 +1,8 @@
use std::{borrow::Cow, fs, path::Path};
use anyhow::{Context, Result};
use blue_build_utils::constants::{CONFIG_PATH, RECIPE_PATH};
use log::warn;
use miette::{Context, IntoDiagnostic, Result};
use serde::{Deserialize, Serialize};
use typed_builder::TypedBuilder;
@ -32,12 +32,14 @@ impl<'a> StagesExt<'a> {
};
let file = fs::read_to_string(&file_path)
.context(format!("Failed to open {}", file_path.display()))?;
.into_diagnostic()
.with_context(|| format!("Failed to open {}", file_path.display()))?;
serde_yaml::from_str::<Self>(&file).map_or_else(
|_| -> Result<Self> {
let mut stage = serde_yaml::from_str::<Stage>(&file)
.map_err(blue_build_utils::serde_yaml_err(&file))?;
.map_err(blue_build_utils::serde_yaml_err(&file))
.into_diagnostic()?;
if let Some(ref mut rf) = stage.required_fields {
rf.modules_ext.modules =
Module::get_modules(&rf.modules_ext.modules, None)?.into();

View file

@ -28,12 +28,12 @@ pub trait BlueBuildCommand {
///
/// # Errors
/// Can return an `anyhow` Error
fn try_run(&mut self) -> anyhow::Result<()>;
fn try_run(&mut self) -> miette::Result<()>;
/// Runs the command and exits if there is an error.
fn run(&mut self) {
if let Err(e) = self.try_run() {
error!("{e}");
error!("Failed:\n{e:?}");
std::process::exit(1);
}
std::process::exit(0);

View file

@ -9,6 +9,7 @@ use clap_complete::Shell;
use colored::Colorize;
use fuzzy_matcher::{skim::SkimMatcherV2, FuzzyMatcher};
use log::{debug, error, trace};
use miette::{IntoDiagnostic, Result};
use requestty::question::{completions, Completions};
use std::time::Duration;
use typed_builder::TypedBuilder;
@ -32,7 +33,7 @@ pub struct BugReportCommand {
}
impl BlueBuildCommand for BugReportCommand {
fn try_run(&mut self) -> anyhow::Result<()> {
fn try_run(&mut self) -> Result<()> {
debug!("Generating bug report for hash: {}\n", shadow::COMMIT_HASH);
debug!("Shadow Versioning:\n{}", shadow::VERSION.trim());
@ -51,7 +52,7 @@ impl BugReportCommand {
/// # Panics
///
/// This function will panic if it fails to get the current shell or terminal version.
pub fn create_bugreport(&self) -> anyhow::Result<()> {
pub fn create_bugreport(&self) -> Result<()> {
let os_info = os_info::get();
let recipe = self.get_recipe();
@ -94,7 +95,7 @@ impl BugReportCommand {
if let Err(e) = open::that(&link) {
println!("Failed to open issue report in your browser: {e}");
println!("Please copy the above report and open an issue manually, or try opening the following link:\n{link}");
return Err(e.into());
return Err(e).into_diagnostic();
}
} else {
println!("{BUG_REPORT_WARNING_MESSAGE}");
@ -125,7 +126,7 @@ impl BugReportCommand {
}
}
fn get_config_file(title: &str, message: &str) -> anyhow::Result<String> {
fn get_config_file(title: &str, message: &str) -> Result<String> {
use std::path::Path;
let question = requestty::Question::input(title)
@ -147,7 +148,7 @@ fn get_config_file(title: &str, message: &str) -> anyhow::Result<String> {
Ok(_) => unreachable!(),
Err(e) => {
trace!("Failed to get file: {}", e);
Err(e.into())
Err(e).into_diagnostic()
}
}
}
@ -276,11 +277,8 @@ fn get_pkg_branch_tag() -> String {
format!("{} ({})", shadow::BRANCH, shadow::LAST_TAG)
}
fn generate_github_issue(
environment: &Environment,
recipe: &Option<Recipe>,
) -> anyhow::Result<String> {
let recipe = serde_yaml::to_string(recipe)?;
fn generate_github_issue(environment: &Environment, recipe: &Option<Recipe>) -> Result<String> {
let recipe = serde_yaml::to_string(recipe).into_diagnostic()?;
let github_template = GithubIssueTemplate::builder()
.bb_version(shadow::PKG_VERSION)
@ -299,7 +297,7 @@ fn generate_github_issue(
.terminal_version(environment.terminal_info.version.clone())
.build();
Ok(github_template.render()?)
github_template.render().into_diagnostic()
}
fn make_github_issue_link(body: &str) -> String {

View file

@ -4,7 +4,6 @@ use std::{
process::Command,
};
use anyhow::{bail, Context, Result};
use blue_build_recipe::Recipe;
use blue_build_utils::{
constants::{
@ -20,6 +19,7 @@ use blue_build_utils::{
use clap::Args;
use colored::Colorize;
use log::{debug, info, trace, warn};
use miette::{bail, Context, IntoDiagnostic, Result};
use typed_builder::TypedBuilder;
use crate::{
@ -330,7 +330,8 @@ impl BuildCommand {
.arg("-p")
.arg(password)
.arg(registry)
.output()?;
.output()
.into_diagnostic()?;
if !login_output.status.success() {
let err_output = String::from_utf8_lossy(&login_output.stderr);
@ -421,7 +422,8 @@ impl BuildCommand {
if !self.force && container_file_path.exists() {
let to_ignore_lines = [format!("/{CONTAINER_FILE}"), format!("/{CONTAINER_FILE}.*")];
let gitignore = fs::read_to_string(GITIGNORE_PATH)
.context(format!("Failed to read {GITIGNORE_PATH}"))?;
.into_diagnostic()
.with_context(|| format!("Failed to read {GITIGNORE_PATH}"))?;
let mut edited_gitignore = gitignore.clone();
@ -434,7 +436,10 @@ impl BuildCommand {
})
.try_for_each(|to_ignore| -> Result<()> {
let containerfile = fs::read_to_string(container_file_path)
.context(format!("Failed to read {}", container_file_path.display()))?;
.into_diagnostic()
.with_context(|| {
format!("Failed to read {}", container_file_path.display())
})?;
let has_label = containerfile
.lines()
@ -467,7 +472,7 @@ impl BuildCommand {
})?;
if edited_gitignore != gitignore {
fs::write(GITIGNORE_PATH, edited_gitignore.as_str())?;
fs::write(GITIGNORE_PATH, edited_gitignore.as_str()).into_diagnostic()?;
}
}
@ -497,7 +502,8 @@ impl BuildCommand {
let output = Command::new("cosign")
.arg("public-key")
.arg("--key=env://COSIGN_PRIVATE_KEY")
.output()?;
.output()
.into_diagnostic()?;
if !output.status.success() {
bail!(
@ -506,9 +512,10 @@ impl BuildCommand {
);
}
let calculated_pub_key = String::from_utf8(output.stdout)?;
let calculated_pub_key = String::from_utf8(output.stdout).into_diagnostic()?;
let found_pub_key = fs::read_to_string(COSIGN_PUB_PATH)
.context(format!("Failed to read {COSIGN_PUB_PATH}"))?;
.into_diagnostic()
.with_context(|| format!("Failed to read {COSIGN_PUB_PATH}"))?;
trace!("calculated_pub_key={calculated_pub_key},found_pub_key={found_pub_key}");
if calculated_pub_key.trim() == found_pub_key.trim() {
@ -523,7 +530,8 @@ impl BuildCommand {
let output = Command::new("cosign")
.arg("public-key")
.arg(format!("--key={COSIGN_PRIV_PATH}"))
.output()?;
.output()
.into_diagnostic()?;
if !output.status.success() {
bail!(
@ -532,9 +540,10 @@ impl BuildCommand {
);
}
let calculated_pub_key = String::from_utf8(output.stdout)?;
let calculated_pub_key = String::from_utf8(output.stdout).into_diagnostic()?;
let found_pub_key = fs::read_to_string(COSIGN_PUB_PATH)
.context(format!("Failed to read {COSIGN_PUB_PATH}"))?;
.into_diagnostic()
.with_context(|| format!("Failed to read {COSIGN_PUB_PATH}"))?;
trace!("calculated_pub_key={calculated_pub_key},found_pub_key={found_pub_key}");
if calculated_pub_key.trim() == found_pub_key.trim() {
@ -622,7 +631,8 @@ fn sign_images(image_name: &str, tag: Option<&str>) -> Result<()> {
.arg("sign")
.arg("--recursive")
.arg(&image_name_digest)
.status()?
.status()
.into_diagnostic()?
.success()
{
info!("Successfully signed image!");
@ -644,7 +654,8 @@ fn sign_images(image_name: &str, tag: Option<&str>) -> Result<()> {
.arg("--certificate-oidc-issuer")
.arg(&cert_oidc)
.arg(&image_name_tag)
.status()?
.status()
.into_diagnostic()?
.success()
{
bail!("Failed to verify image!");
@ -661,7 +672,8 @@ fn sign_images(image_name: &str, tag: Option<&str>) -> Result<()> {
.arg("sign")
.arg("--recursive")
.arg(&image_name_digest)
.status()?
.status()
.into_diagnostic()?
.success()
{
info!("Successfully signed image!");
@ -677,7 +689,8 @@ fn sign_images(image_name: &str, tag: Option<&str>) -> Result<()> {
.arg("--certificate-oidc-issuer")
.arg(GITHUB_TOKEN_ISSUER_URL)
.arg(&image_name_tag)
.status()?
.status()
.into_diagnostic()?
.success()
{
bail!("Failed to verify image!");
@ -699,7 +712,8 @@ fn sign_priv_public_pair_env(image_digest: &str, image_name_tag: &str) -> Result
.arg("--key=env://COSIGN_PRIVATE_KEY")
.arg("--recursive")
.arg(image_digest)
.status()?
.status()
.into_diagnostic()?
.success()
{
info!("Successfully signed image!");
@ -713,7 +727,8 @@ fn sign_priv_public_pair_env(image_digest: &str, image_name_tag: &str) -> Result
.arg("verify")
.arg(format!("--key={COSIGN_PUB_PATH}"))
.arg(image_name_tag)
.status()?
.status()
.into_diagnostic()?
.success()
{
bail!("Failed to verify image!");
@ -732,7 +747,8 @@ fn sign_priv_public_pair_file(image_digest: &str, image_name_tag: &str) -> Resul
.arg(format!("--key={COSIGN_PRIV_PATH}"))
.arg("--recursive")
.arg(image_digest)
.status()?
.status()
.into_diagnostic()?
.success()
{
info!("Successfully signed image!");
@ -746,7 +762,8 @@ fn sign_priv_public_pair_file(image_digest: &str, image_name_tag: &str) -> Resul
.arg("verify")
.arg(format!("--key={COSIGN_PUB_PATH}"))
.arg(image_name_tag)
.status()?
.status()
.into_diagnostic()?
.success()
{
bail!("Failed to verify image!");

View file

@ -1,5 +1,6 @@
use clap::{Args, CommandFactory};
use clap_complete::{generate, Shell as CompletionShell};
use miette::Result;
use crate::commands::BlueBuildArgs;
@ -12,7 +13,7 @@ pub struct CompletionsCommand {
}
impl BlueBuildCommand for CompletionsCommand {
fn try_run(&mut self) -> anyhow::Result<()> {
fn try_run(&mut self) -> Result<()> {
log::debug!("Generating completions for {}", self.shell);
generate(

View file

@ -3,7 +3,6 @@ use std::{
path::{Path, PathBuf},
};
use anyhow::Result;
use blue_build_recipe::Recipe;
use blue_build_template::{ContainerFileTemplate, Template};
use blue_build_utils::{
@ -15,6 +14,7 @@ use blue_build_utils::{
};
use clap::{crate_version, Args};
use log::{debug, info, trace, warn};
use miette::{IntoDiagnostic, Result};
use typed_builder::TypedBuilder;
use crate::{drivers::Driver, shadow};
@ -104,7 +104,8 @@ impl GenerateCommand {
if self.display_full_recipe {
if let Some(output) = self.output.as_ref() {
std::fs::write(output, serde_yaml::to_string(&recipe_de)?)?;
std::fs::write(output, serde_yaml::to_string(&recipe_de).into_diagnostic()?)
.into_diagnostic()?;
} else {
syntax_highlighting::print_ser(&recipe_de, "yml", self.syntax_theme)?;
}
@ -132,12 +133,12 @@ impl GenerateCommand {
})
.build();
let output_str = template.render()?;
let output_str = template.render().into_diagnostic()?;
if let Some(output) = self.output.as_ref() {
debug!("Templating to file {}", output.display());
trace!("Containerfile:\n{output_str}");
std::fs::write(output, output_str)?;
std::fs::write(output, output_str).into_diagnostic()?;
} else {
debug!("Templating to stdout");
syntax_highlighting::print(&output_str, "Dockerfile", self.syntax_theme)?;

View file

@ -4,11 +4,11 @@ use std::{
process::Command,
};
use anyhow::{bail, Result};
use blue_build_recipe::Recipe;
use blue_build_utils::constants::{ARCHIVE_SUFFIX, LOCAL_BUILD};
use clap::Args;
use log::{debug, info, trace};
use miette::{bail, IntoDiagnostic, Result};
use typed_builder::TypedBuilder;
use users::{Users, UsersCache};
@ -84,12 +84,16 @@ impl BlueBuildCommand for UpgradeCommand {
Command::new("rpm-ostree")
.arg("upgrade")
.arg("--reboot")
.status()?
.status()
.into_diagnostic()?
} else {
info!("Upgrading image {image_name}");
trace!("rpm-ostree upgrade");
Command::new("rpm-ostree").arg("upgrade").status()?
Command::new("rpm-ostree")
.arg("upgrade")
.status()
.into_diagnostic()?
};
if status.success() {
@ -146,7 +150,8 @@ impl BlueBuildCommand for RebaseCommand {
.arg("rebase")
.arg("--reboot")
.arg(rebase_url)
.status()?
.status()
.into_diagnostic()?
} else {
info!("Rebasing image {image_name}");
@ -154,7 +159,8 @@ impl BlueBuildCommand for RebaseCommand {
Command::new("rpm-ostree")
.arg("rebase")
.arg(rebase_url)
.status()?
.status()
.into_diagnostic()?
};
if status.success() {
@ -198,10 +204,10 @@ fn clean_local_build_dir(image_name: &str, rebase: bool) -> Result<()> {
if local_build_path.exists() {
debug!("Cleaning out build dir {LOCAL_BUILD}");
let entries = fs::read_dir(LOCAL_BUILD)?;
let entries = fs::read_dir(LOCAL_BUILD).into_diagnostic()?;
for entry in entries {
let entry = entry?;
let entry = entry.into_diagnostic()?;
let path = entry.path();
trace!("Found {}", path.display());
@ -211,7 +217,7 @@ fn clean_local_build_dir(image_name: &str, rebase: bool) -> Result<()> {
continue;
}
trace!("Removing {}", path.display());
fs::remove_file(path)?;
fs::remove_file(path).into_diagnostic()?;
}
}
} else {
@ -219,7 +225,7 @@ fn clean_local_build_dir(image_name: &str, rebase: bool) -> Result<()> {
"Creating build output dir at {}",
local_build_path.display()
);
fs::create_dir_all(local_build_path)?;
fs::create_dir_all(local_build_path).into_diagnostic()?;
}
Ok(())

View file

@ -4,7 +4,6 @@ use std::{
time::Duration,
};
use anyhow::{bail, Result};
use blue_build_recipe::Recipe;
use blue_build_utils::{
constants::{ARCHIVE_SUFFIX, LOCAL_BUILD, OCI_ARCHIVE, OSTREE_UNVERIFIED_IMAGE},
@ -14,6 +13,7 @@ use clap::Args;
use colored::Colorize;
use indicatif::ProgressBar;
use log::{debug, trace, warn};
use miette::{bail, IntoDiagnostic, Result};
use tempdir::TempDir;
use typed_builder::TypedBuilder;
@ -64,7 +64,7 @@ impl BlueBuildCommand for SwitchCommand {
bail!("There is a transaction in progress. Please cancel it using `rpm-ostree cancel`");
}
let tempdir = TempDir::new("oci-archive")?;
let tempdir = TempDir::new("oci-archive").into_diagnostic()?;
trace!("{tempdir:?}");
BuildCommand::builder()
@ -144,7 +144,8 @@ impl SwitchCommand {
.status_image_ref_progress(
format!("{}", archive_path.display()),
"Switching to new image",
)?;
)
.into_diagnostic()?;
if !status.success() {
bail!("Failed to switch to new image!");
@ -164,7 +165,11 @@ impl SwitchCommand {
progress.set_message(format!("Moving image archive to {}...", to.display()));
trace!("sudo mv {} {}", from.display(), to.display());
let status = Command::new("sudo").arg("mv").args([from, to]).status()?;
let status = Command::new("sudo")
.arg("mv")
.args([from, to])
.status()
.into_diagnostic()?;
progress.finish_and_clear();
@ -191,9 +196,11 @@ impl SwitchCommand {
let output = String::from_utf8(
Command::new("sudo")
.args(["ls", LOCAL_BUILD])
.output()?
.output()
.into_diagnostic()?
.stdout,
)?;
)
.into_diagnostic()?;
trace!("{output}");
@ -214,7 +221,8 @@ impl SwitchCommand {
let status = Command::new("sudo")
.args(["rm", "-f"])
.arg(files)
.status()?;
.status()
.into_diagnostic()?;
progress.finish_and_clear();
@ -230,7 +238,8 @@ impl SwitchCommand {
let status = Command::new("sudo")
.args(["mkdir", "-p", LOCAL_BUILD])
.status()?;
.status()
.into_diagnostic()?;
if !status.success() {
bail!("Failed to create directory {LOCAL_BUILD}");

View file

@ -10,10 +10,10 @@ use std::{
sync::{Arc, Mutex},
};
use anyhow::{anyhow, bail, Result};
use blue_build_recipe::Recipe;
use blue_build_utils::constants::IMAGE_VERSION_LABEL;
use log::{debug, info, trace};
use miette::{bail, miette, Result};
use once_cell::sync::Lazy;
use semver::{Version, VersionReq};
use typed_builder::TypedBuilder;
@ -202,7 +202,7 @@ pub trait BuildDriver: Sync + Send {
let image = opts
.image
.as_ref()
.ok_or_else(|| anyhow!("Image is required in order to tag"))?;
.ok_or_else(|| miette!("Image is required in order to tag"))?;
debug!("Tagging all images");
for tag in opts.tags.as_ref() {
@ -369,13 +369,14 @@ impl Driver<'_> {
/// # Errors
/// Will error if the image doesn't have OS version info
/// or we are unable to lock a mutex.
///
/// # Panics
/// Panics if the mutex fails to lock.
pub fn get_os_version(recipe: &Recipe) -> Result<u64> {
trace!("Driver::get_os_version({recipe:#?})");
let image = format!("{}:{}", &recipe.base_image, &recipe.image_version);
let mut os_version_lock = OS_VERSION
.lock()
.map_err(|e| anyhow!("Unable set OS_VERSION {e}"))?;
let mut os_version_lock = OS_VERSION.lock().expect("Should lock");
let entry = os_version_lock.get(&image);
@ -389,8 +390,9 @@ impl Driver<'_> {
let inspection = INSPECT_DRIVER.get_metadata(&inspect_opts)?;
let os_version = inspection.get_version().ok_or_else(|| {
anyhow!(
"Unable to get the OS version from the labels. Please check with the image author about using '{IMAGE_VERSION_LABEL}' to report the os version."
miette!(
help = format!("Please check with the image author about using '{IMAGE_VERSION_LABEL}' to report the os version."),
"Unable to get the OS version from the labels"
)
})?;
trace!("os_version: {os_version}");

View file

@ -1,8 +1,8 @@
use std::process::Command;
use anyhow::{bail, Result};
use blue_build_utils::logging::CommandLogging;
use log::{error, info, trace};
use miette::{bail, IntoDiagnostic, Result};
use semver::Version;
use serde::Deserialize;
@ -33,10 +33,12 @@ impl DriverVersion for BuildahDriver {
let output = Command::new("buildah")
.arg("version")
.arg("--json")
.output()?;
.output()
.into_diagnostic()?;
let version_json: BuildahVersionJson = serde_json::from_slice(&output.stdout)
.inspect_err(|e| error!("{e}: {}", String::from_utf8_lossy(&output.stdout)))?;
.inspect_err(|e| error!("{e}: {}", String::from_utf8_lossy(&output.stdout)))
.into_diagnostic()?;
trace!("{version_json:#?}");
Ok(version_json.version)
@ -62,7 +64,9 @@ impl BuildDriver for BuildahDriver {
.arg(opts.containerfile.as_ref())
.arg("-t")
.arg(opts.image.as_ref());
let status = command.status_image_ref_progress(&opts.image, "Building Image")?;
let status = command
.status_image_ref_progress(&opts.image, "Building Image")
.into_diagnostic()?;
if status.success() {
info!("Successfully built {}", opts.image);
@ -80,7 +84,8 @@ impl BuildDriver for BuildahDriver {
.arg("tag")
.arg(opts.src_image.as_ref())
.arg(opts.dest_image.as_ref())
.status()?;
.status()
.into_diagnostic()?;
if status.success() {
info!("Successfully tagged {}!", opts.dest_image);
@ -102,7 +107,9 @@ impl BuildDriver for BuildahDriver {
opts.compression_type.unwrap_or_default()
))
.arg(opts.image.as_ref());
let status = command.status_image_ref_progress(&opts.image, "Pushing Image")?;
let status = command
.status_image_ref_progress(&opts.image, "Pushing Image")
.into_diagnostic()?;
if status.success() {
info!("Successfully pushed {}!", opts.image);
@ -129,7 +136,8 @@ impl BuildDriver for BuildahDriver {
.arg("-p")
.arg(password)
.arg(registry)
.output()?;
.output()
.into_diagnostic()?;
if !output.status.success() {
let err_out = String::from_utf8_lossy(&output.stderr);

View file

@ -6,7 +6,6 @@ use std::{
time::Duration,
};
use anyhow::{anyhow, bail, Result};
use blue_build_utils::{
constants::{BB_BUILDKIT_CACHE_GHA, CONTAINER_FILE, DOCKER_HOST, SKOPEO_IMAGE},
logging::{CommandLogging, Logger},
@ -14,6 +13,7 @@ use blue_build_utils::{
};
use indicatif::{ProgressBar, ProgressStyle};
use log::{info, trace, warn};
use miette::{bail, IntoDiagnostic, Result};
use once_cell::sync::Lazy;
use semver::Version;
use serde::Deserialize;
@ -50,9 +50,7 @@ impl DockerDriver {
fn setup() -> Result<()> {
trace!("DockerDriver::setup()");
let mut lock = DOCKER_SETUP
.lock()
.map_err(|e| anyhow!("Failed to lock DOCKER_SETUP: {e}"))?;
let mut lock = DOCKER_SETUP.lock().expect("Should lock");
if *lock {
drop(lock);
@ -64,13 +62,14 @@ impl DockerDriver {
.arg("buildx")
.arg("ls")
.arg("--format={{.Name}}")
.output()?;
.output()
.into_diagnostic()?;
if !ls_out.status.success() {
bail!("{}", String::from_utf8_lossy(&ls_out.stderr));
}
let ls_out = String::from_utf8(ls_out.stdout)?;
let ls_out = String::from_utf8(ls_out.stdout).into_diagnostic()?;
trace!("{ls_out}");
@ -82,7 +81,8 @@ impl DockerDriver {
.arg("--bootstrap")
.arg("--driver=docker-container")
.arg("--name=bluebuild")
.output()?;
.output()
.into_diagnostic()?;
if !create_out.status.success() {
bail!("{}", String::from_utf8_lossy(&create_out.stderr));
@ -105,9 +105,11 @@ impl DriverVersion for DockerDriver {
.arg("version")
.arg("-f")
.arg("json")
.output()?;
.output()
.into_diagnostic()?;
let version_json: DockerVersionJson = serde_json::from_slice(&output.stdout)?;
let version_json: DockerVersionJson =
serde_json::from_slice(&output.stdout).into_diagnostic()?;
Ok(version_json.client.version)
}
@ -129,7 +131,8 @@ impl BuildDriver for DockerDriver {
.arg("-f")
.arg(opts.containerfile.as_ref())
.arg(".")
.status()?;
.status()
.into_diagnostic()?;
if status.success() {
info!("Successfully built {}", opts.image);
@ -147,7 +150,8 @@ impl BuildDriver for DockerDriver {
.arg("tag")
.arg(opts.src_image.as_ref())
.arg(opts.dest_image.as_ref())
.status()?;
.status()
.into_diagnostic()?;
if status.success() {
info!("Successfully tagged {}!", opts.dest_image);
@ -164,7 +168,8 @@ impl BuildDriver for DockerDriver {
let status = Command::new("docker")
.arg("push")
.arg(opts.image.as_ref())
.status()?;
.status()
.into_diagnostic()?;
if status.success() {
info!("Successfully pushed {}!", opts.image);
@ -191,7 +196,8 @@ impl BuildDriver for DockerDriver {
.arg("-p")
.arg(password)
.arg(registry)
.output()?;
.output()
.into_diagnostic()?;
if !output.status.success() {
let err_out = String::from_utf8_lossy(&output.stderr);
@ -287,7 +293,8 @@ impl BuildDriver for DockerDriver {
command.arg(".");
if command
.status_image_ref_progress(&final_image, "Building Image")?
.status_image_ref_progress(&final_image, "Building Image")
.into_diagnostic()?
.success()
{
if opts.push {
@ -318,12 +325,14 @@ impl InspectDriver for DockerDriver {
);
progress.enable_steady_tick(Duration::from_millis(100));
let output = self.run_output(
&RunOpts::builder()
.image(SKOPEO_IMAGE)
.args(&["inspect".to_string(), url.clone()])
.build(),
)?;
let output = self
.run_output(
&RunOpts::builder()
.image(SKOPEO_IMAGE)
.args(&["inspect".to_string(), url.clone()])
.build(),
)
.into_diagnostic()?;
progress.finish();
Logger::multi_progress().remove(&progress);
@ -334,7 +343,7 @@ impl InspectDriver for DockerDriver {
bail!("Failed to inspect image {url}")
}
Ok(serde_json::from_slice(&output.stdout)?)
serde_json::from_slice(&output.stdout).into_diagnostic()
}
}

View file

@ -4,7 +4,6 @@ use std::{
time::Duration,
};
use anyhow::{bail, Result};
use blue_build_utils::{
constants::SKOPEO_IMAGE,
logging::{CommandLogging, Logger},
@ -13,6 +12,7 @@ use blue_build_utils::{
use colored::Colorize;
use indicatif::{ProgressBar, ProgressStyle};
use log::{debug, error, info, trace, warn};
use miette::{bail, IntoDiagnostic, Result};
use semver::Version;
use serde::Deserialize;
use tempdir::TempDir;
@ -55,10 +55,12 @@ impl DriverVersion for PodmanDriver {
.arg("version")
.arg("-f")
.arg("json")
.output()?;
.output()
.into_diagnostic()?;
let version_json: PodmanVersionJson = serde_json::from_slice(&output.stdout)
.inspect_err(|e| error!("{e}: {}", String::from_utf8_lossy(&output.stdout)))?;
.inspect_err(|e| error!("{e}: {}", String::from_utf8_lossy(&output.stdout)))
.into_diagnostic()?;
trace!("{version_json:#?}");
Ok(version_json.client.version)
@ -85,7 +87,9 @@ impl BuildDriver for PodmanDriver {
.arg("-t")
.arg(opts.image.as_ref())
.arg(".");
let status = command.status_image_ref_progress(&opts.image, "Building Image")?;
let status = command
.status_image_ref_progress(&opts.image, "Building Image")
.into_diagnostic()?;
if status.success() {
info!("Successfully built {}", opts.image);
@ -103,7 +107,8 @@ impl BuildDriver for PodmanDriver {
.arg("tag")
.arg(opts.src_image.as_ref())
.arg(opts.dest_image.as_ref())
.status()?;
.status()
.into_diagnostic()?;
if status.success() {
info!("Successfully tagged {}!", opts.dest_image);
@ -125,7 +130,9 @@ impl BuildDriver for PodmanDriver {
opts.compression_type.unwrap_or_default()
))
.arg(opts.image.as_ref());
let status = command.status_image_ref_progress(&opts.image, "Pushing Image")?;
let status = command
.status_image_ref_progress(&opts.image, "Pushing Image")
.into_diagnostic()?;
if status.success() {
info!("Successfully pushed {}!", opts.image);
@ -152,7 +159,8 @@ impl BuildDriver for PodmanDriver {
.arg("-p")
.arg(password)
.arg(registry)
.output()?;
.output()
.into_diagnostic()?;
if !output.status.success() {
let err_out = String::from_utf8_lossy(&output.stderr);
@ -179,12 +187,14 @@ impl InspectDriver for PodmanDriver {
);
progress.enable_steady_tick(Duration::from_millis(100));
let output = self.run_output(
&RunOpts::builder()
.image(SKOPEO_IMAGE)
.args(&["inspect".to_string(), url.clone()])
.build(),
)?;
let output = self
.run_output(
&RunOpts::builder()
.image(SKOPEO_IMAGE)
.args(&["inspect".to_string(), url.clone()])
.build(),
)
.into_diagnostic()?;
progress.finish();
Logger::multi_progress().remove(&progress);
@ -194,7 +204,7 @@ impl InspectDriver for PodmanDriver {
} else {
bail!("Failed to inspect image {url}");
}
Ok(serde_json::from_slice(&output.stdout)?)
serde_json::from_slice(&output.stdout).into_diagnostic()
}
}

View file

@ -3,10 +3,10 @@ use std::{
time::Duration,
};
use anyhow::{bail, Result};
use blue_build_utils::logging::Logger;
use indicatif::{ProgressBar, ProgressStyle};
use log::{debug, trace};
use miette::{bail, IntoDiagnostic, Result};
use crate::image_metadata::ImageMetadata;
@ -36,7 +36,8 @@ impl InspectDriver for SkopeoDriver {
.arg("inspect")
.arg(&url)
.stderr(Stdio::inherit())
.output()?;
.output()
.into_diagnostic()?;
progress.finish();
Logger::multi_progress().remove(&progress);
@ -46,6 +47,6 @@ impl InspectDriver for SkopeoDriver {
} else {
bail!("Failed to inspect image {url}")
}
Ok(serde_json::from_slice(&output.stdout)?)
serde_json::from_slice(&output.stdout).into_diagnostic()
}
}

View file

@ -1,7 +1,7 @@
use std::{borrow::Cow, path::Path, process::Command};
use anyhow::{bail, Result};
use log::trace;
use miette::{bail, IntoDiagnostic, Result};
use serde::Deserialize;
#[derive(Debug, Clone, Deserialize)]
@ -29,7 +29,8 @@ impl<'a> RpmOstreeStatus<'a> {
trace!("rpm-ostree status --json");
let output = Command::new("rpm-ostree")
.args(["status", "--json"])
.output()?;
.output()
.into_diagnostic()?;
if !output.status.success() {
bail!("Failed to get `rpm-ostree` status!");
@ -37,7 +38,7 @@ impl<'a> RpmOstreeStatus<'a> {
trace!("{}", String::from_utf8_lossy(&output.stdout));
Ok(serde_json::from_slice(&output.stdout)?)
serde_json::from_slice(&output.stdout).into_diagnostic()
}
/// Checks if there is a transaction in progress.

View file

@ -9,6 +9,7 @@ license.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anyhow = "1"
atty = "0.2"
base64 = "0.22.1"
blake2 = "0.10.6"
@ -23,7 +24,6 @@ signal-hook = { version = "0.3.17", features = ["extended-siginfo"] }
syntect = "5"
which = "6"
anyhow.workspace = true
chrono.workspace = true
clap = { workspace = true, features = ["derive"] }
colored.workspace = true
@ -31,6 +31,7 @@ format_serde_error.workspace = true
indicatif.workspace = true
indicatif-log-bridge.workspace = true
log.workspace = true
miette.workspace = true
once_cell.workspace = true
tempdir.workspace = true
serde.workspace = true

View file

@ -12,7 +12,6 @@ use std::{
time::Duration,
};
use anyhow::{anyhow, Result};
use base64::prelude::*;
use blake2::{
digest::{Update, VariableOutput},
@ -20,6 +19,7 @@ use blake2::{
};
use format_serde_error::SerdeError;
use log::trace;
use miette::{miette, IntoDiagnostic, Result};
use crate::constants::CONTAINER_FILE;
@ -35,14 +35,15 @@ pub fn check_command_exists(command: &str) -> Result<()> {
trace!("which {command}");
if Command::new("which")
.arg(command)
.output()?
.output()
.into_diagnostic()?
.status
.success()
{
trace!("Command {command} does exist");
Ok(())
} else {
Err(anyhow!(
Err(miette!(
"Command {command} doesn't exist and is required to build the image"
))
}
@ -69,9 +70,9 @@ pub fn serde_yaml_err(contents: &str) -> impl Fn(serde_yaml::Error) -> SerdeErro
///
/// # Errors
/// Will error when retries have been expended.
pub fn retry<V, F>(attempts: u8, delay: u64, f: F) -> anyhow::Result<V>
pub fn retry<V, F>(attempts: u8, delay: u64, f: F) -> miette::Result<V>
where
F: Fn() -> anyhow::Result<V>,
F: Fn() -> miette::Result<V>,
{
let mut attempts = attempts;
loop {
@ -100,9 +101,9 @@ pub fn generate_containerfile_path<T: AsRef<Path>>(path: T) -> Result<PathBuf> {
const HASH_SIZE: usize = 8;
let mut buf = [0u8; HASH_SIZE];
let mut hasher = Blake2bVar::new(HASH_SIZE)?;
let mut hasher = Blake2bVar::new(HASH_SIZE).into_diagnostic()?;
hasher.update(path.as_ref().as_os_str().as_bytes());
hasher.finalize_variable(&mut buf)?;
hasher.finalize_variable(&mut buf).into_diagnostic()?;
Ok(PathBuf::from(format!(
"{CONTAINER_FILE}.{}",

View file

@ -1,6 +1,6 @@
use anyhow::{anyhow, Result};
use clap::ValueEnum;
use log::trace;
use miette::{miette, IntoDiagnostic, Result};
use serde::ser::Serialize;
use syntect::{dumps, easy::HighlightLines, highlighting::ThemeSet, parsing::SyntaxSet};
@ -42,7 +42,8 @@ pub fn highlight(file: &str, file_type: &str, theme: Option<DefaultThemes>) -> R
dumps::from_uncompressed_data(include_bytes!(concat!(
env!("OUT_DIR"),
"/docker_syntax.bin"
)))?
)))
.into_diagnostic()?
} else {
SyntaxSet::load_defaults_newlines()
};
@ -50,18 +51,18 @@ pub fn highlight(file: &str, file_type: &str, theme: Option<DefaultThemes>) -> R
let syntax = ss
.find_syntax_by_extension(file_type)
.ok_or_else(|| anyhow!("Failed to get syntax"))?;
.ok_or_else(|| miette!("Failed to get syntax"))?;
let mut h = HighlightLines::new(
syntax,
ts.themes
.get(theme.unwrap_or_default().to_string().as_str())
.ok_or_else(|| anyhow!("Failed to get highlight theme"))?,
.ok_or_else(|| miette!("Failed to get highlight theme"))?,
);
let mut highlighted_lines: Vec<String> = vec![];
for line in file.lines() {
highlighted_lines.push(syntect::util::as_24_bit_terminal_escaped(
&h.highlight_line(line, &ss)?,
&h.highlight_line(line, &ss).into_diagnostic()?,
false,
));
}
@ -83,7 +84,11 @@ pub fn highlight_ser<T: Serialize + std::fmt::Debug>(
theme: Option<DefaultThemes>,
) -> Result<String> {
trace!("syntax_highlighting::highlight_ser(file, {file_type}, {theme:?})");
highlight(serde_yaml::to_string(file)?.as_str(), file_type, theme)
highlight(
serde_yaml::to_string(file).into_diagnostic()?.as_str(),
file_type,
theme,
)
}
/// Prints the file with syntax highlighting.
@ -108,6 +113,10 @@ pub fn print_ser<T: Serialize + std::fmt::Debug>(
theme: Option<DefaultThemes>,
) -> Result<()> {
trace!("syntax_highlighting::print_ser(file, {file_type}, {theme:?})");
print(serde_yaml::to_string(file)?.as_str(), file_type, theme)?;
print(
serde_yaml::to_string(file).into_diagnostic()?.as_str(),
file_type,
theme,
)?;
Ok(())
}