Create / Use Presets via Flavors
At the end of the previous lesson we saw ok_or_else missing on Result when trying to hook uniformly. Can we hook something available on both Result and Option? The Context::with_context method from anyhow fits perfectly: on Option it converts None into an anyhow::Result::Err.
use anyhow::{Context, Result};
use hooq::hooq;
#[hooq]
#[hooq::method(.with_context(|| {
let path = $path;
let line = $line;
let col = $col;
let expr = ::hooq::summary!($source);
format!("[{path}:{line}:{col}]\n{expr}")
}))]
fn display_name_by_mista(val: &toml::Value) -> Result<()> {
let name = val.get("package")?.get("name")?.as_str()?;
if name.contains("4") {
return Err(anyhow::anyhow!(
"name `{name}` contains '4'. Guido Mista disallow this."
));
}
println!("Mista「name: {name}」");
Ok(())
}
#[hooq]
#[hooq::method(.with_context(|| {
let path = $path;
let line = $line;
let col = $col;
let expr = ::hooq::summary!($source);
format!("[{path}:{line}:{col}]\n{expr}")
}))]
fn main() -> Result<()> {
let path = std::env::args().nth(1).unwrap_or("Cargo.toml".to_string());
let cargo_toml: toml::Value = toml::from_str(&std::fs::read_to_string(path)?)?;
display_name_by_mista(&cargo_toml)?;
Ok(())
}
Run to produce an error: the extra .with_context(...) calls accumulate, giving a trace.
Error: [mdbook-source-code/tutorial-4-with-anyhow/src/main.rs:41:39]
41> display_name_by_mista(&cargo_toml)?
|
Caused by:
0: [mdbook-source-code/tutorial-4-with-anyhow/src/main.rs:17:9]
17> return Err(anyhow::anyhow!(
18| "name `{name}` contains '4'. Guido Mista disallow this."
19| ))
|
1: name `tutorial-4-with-anyhow` contains '4'. Guido Mista disallow this.
Because this is a frequent pattern hooq provides a preset—the anyhow flavor. Presets are called “flavors” in hooq. There are also flavors for log, eyre, and tracing.
Change #[hooq] to #[hooq(anyhow)] to enable the flavor:
use anyhow::Result;
use hooq::hooq;
#[hooq(anyhow)]
fn display_name_by_mista(val: &toml::Value) -> Result<()> {
let name = val.get("package")?.get("name")?.as_str()?;
if name.contains("4") {
return Err(anyhow::anyhow!(
"name `{name}` contains '4'. Guido Mista disallow this."
));
}
println!("Mista「name: {name}」");
Ok(())
}
#[hooq(anyhow)]
fn main() -> Result<()> {
let path = std::env::args().nth(1).unwrap_or("Cargo.toml".to_string());
let cargo_toml: toml::Value = toml::from_str(&std::fs::read_to_string(path)?)?;
display_name_by_mista(&cargo_toml)?;
Ok(())
}
Output matches the manual version.
See Flavors for all built‑in presets.
Define Custom Flavors
You can define flavors in a hooq.toml placed at the crate root (CARGO_MANIFEST_DIR). Example:
[my_flavor]
method = """.inspect_err(|_| {
eprintln!("Error @ Line {}: Col: {}\n{}", $line, $col, ::hooq::summary!($source));
})
.inspect(|_| {
println!("Success @ Line {}: Col: {}\n{}", $line, $col, ::hooq::summary!($source));
})"""
hook_targets = ["?", "return", "tail_expr"]
tail_expr_idents = ["Ok", "Err"]
result_types = ["Result"]
hook_in_macros = true
[my_flavor.ok_or_else]
method = """
.ok_or_else(|| {
format!("[Line: {}, {}]\n{}",
$line,
$nth,
::hooq::summary!($source),
)
})
.$so_far"""
Meaning of the keys:
| Key | Effect |
|---|---|
method | Method to hook |
hook_targets | Which of ?, return, tail_expr to hook |
tail_expr_idents | Idents (e.g. Err) that force hooking on tail / return |
result_types | Return type idents (e.g. Result) whose tail/return values are considered for hooks |
my_flavor.ok_or_else is a sub‑flavor, inheriting settings and overriding a subset.
For all available fields and precedence rules see Attributes and Flavors.
Use the flavor in code:
use hooq::hooq;
#[hooq(my_flavor)]
fn display_name_by_mista(val: &toml::Value) -> Result<(), String> {
// Method can be overridden by the one in flavor!
#[hooq::method = my_flavor::ok_or_else]
let name = val.get("package")?.get("name")?.as_str()?;
if name.contains("4") {
return Err(format!(
"name `{name}` contains '4'. Guido Mista disallow this."
));
}
println!("Mista「name: {name}」");
Ok(())
}
#[hooq(my_flavor)]
fn main() -> Result<(), Box<dyn std::error::Error>> {
let path = std::env::args().nth(1).unwrap_or("Cargo.toml".to_string());
let cargo_toml: toml::Value = toml::from_str(&std::fs::read_to_string(path)?)?;
display_name_by_mista(&cargo_toml)?;
Ok(())
}
#[hooq::method = flavor_name] performs partial application of a flavor’s settings. Other fields also support this via #[hooq::field = flavor_name]. See Attributes (partial application).
Subtle note: $so_far represents the chain after initial flavor application; you cannot reference $so_far inside the base flavor method itself. Thus #[hooq(my_flavor::ok_or_else)] cannot include .$so_far, while #[hooq::method = my_flavor::ok_or_else] can.