Flavors
Flavors are presets that bundle hooq settings. Built‑in flavors:
| Name | feature | Contents |
|---|---|---|
| default | - | Default when nothing is specified; overridable via hooq.toml. |
| empty | - | Disables hooking; inert attributes still processed. Not overridable. |
| hook | - | Inserts a hook method taking hooq::HooqMeta; designed for user traits. Overridable. |
| anyhow | anyhow | Inserts .with_context(...). Overridable. |
| eyre | eyre | Inserts .wrap_err_with(...). Overridable. |
| log | log | Inserts inspect_err that calls ::log::error!. Overridable. |
| tracing | tracing | Inserts inspect_err that calls ::tracing::error!. Overridable. |
Flavor features are part of the default feature set, so you usually do not need to enable them explicitly.
Users can define flavors in a hooq.toml at crate root.
User‑Defined Flavors
hooq.toml uses table names as flavor names with fields:
| Field | Type | Description |
|---|---|---|
| trait_uses | array of strings | Trait paths to import. |
| method | string | Method/expression to insert/replace. |
| hook_targets | array of strings | Any of "?", "return", "tail_expr". |
| tail_expr_idents | array of strings | Idents like "Err". |
| ignore_tail_expr_idents | array of strings | Idents like "Ok". |
| result_types | array of strings | Return type idents like "Result". |
| hook_in_macros | bool | true or false. |
| bindings | inline table | Arbitrary bindings; note string literals must be quoted with \". |
All built‑in (except empty) can be overridden by defining the same table name. Sub‑tables other than bindings are sub‑flavors and inherit from their parent.
See Attributes for how to apply flavors.
Example hooq.toml:
[my_flavor]
trait_uses = ["sub::MyTrait"]
method = """.inspect_err(|_| {
let _ = $xxx;
})"""
hook_targets = ["?", "return", "tail_expr"]
tail_expr_idents = ["Err"]
ignore_tail_expr_idents = ["Ok"]
result_types = ["Result"]
hook_in_macros = true
bindings = { xxx = "\"my_flavor\"" }
[my_flavor.sub_flavor]
bindings = { xxx = "\"my_flavor_sub\"" }
Usage:
use hooq::hooq;
fn failable<T>(val: T) -> Result<T, String> {
Ok(val)
}
mod sub {
pub trait MyTrait {}
}
#[hooq(my_flavor)]
fn main() -> Result<(), Box<dyn std::error::Error>> {
failable(())?;
#[hooq::flavor = my_flavor::sub_flavor]
failable(())?;
Ok(())
}
Expansion:
#![feature(prelude_import)]
#[macro_use]
extern crate std;
#[prelude_import]
use std::prelude::rust_2024::*;
use hooq::hooq;
fn failable<T>(val: T) -> Result<T, String> {
Ok(val)
}
mod sub {
pub trait MyTrait {}
}
#[allow(unused)]
use sub::MyTrait as _;
fn main() -> Result<(), Box<dyn std::error::Error>> {
failable(())
.inspect_err(|_| {
let _ = "my_flavor";
})?;
failable(())
.inspect_err(|_| {
let _ = "my_flavor_sub";
})?;
Ok(())
}
default
Default configuration when using #[hooq].
It is configured as follows. (To keep the documentation consistent, this is excerpted directly from the source code; the same applies below.)
use std::collections::HashMap;
use std::path::PathBuf;
use std::rc::Rc;
use std::sync::{LazyLock, Mutex};
use proc_macro2::TokenStream;
use syn::{Expr, Path, parse_quote};
pub use crate::impls::flavor::flavor_path::FlavorPath;
use crate::impls::flavor::toml_load::HooqToml;
use crate::impls::inert_attr::context::HookTargetSwitch;
use crate::impls::method::Method;
use crate::impls::utils::unexpected_error_message::UNEXPECTED_ERROR_MESSAGE;
mod flavor_path;
mod presets;
mod toml_load;
#[derive(Debug, Clone)]
pub struct Flavor {
pub trait_uses: Vec<Path>,
pub method: Method,
pub hook_targets: HookTargetSwitch,
pub tail_expr_idents: Vec<String>,
pub ignore_tail_expr_idents: Vec<String>,
pub result_types: Vec<String>,
pub hook_in_macros: bool,
pub bindings: HashMap<String, Rc<Expr>>,
pub sub_flavors: HashMap<String, Flavor>,
}
impl Default for Flavor {
fn default() -> Self {
Self {
trait_uses: Vec::new(),
method: default_method(),
hook_targets: HookTargetSwitch {
question: true,
return_: true,
tail_expr: true,
},
tail_expr_idents: vec!["Err".to_string()],
ignore_tail_expr_idents: vec!["Ok".to_string()],
result_types: vec!["Result".to_string()],
hook_in_macros: true,
bindings: HashMap::new(),
sub_flavors: HashMap::new(),
}
}
}
fn default_method() -> Method {
// NOTE:
// $path や $line は eprintln! に直接埋め込みたいところだが、
// CI側のテストの関係でこのようになっている
let excerpted_helpers_path = crate::impls::utils::get_source_excerpt_helpers_name_space();
let res: TokenStream = parse_quote! {
.inspect_err(|e| {
let path = $path;
let line = $line;
let col = $col;
let expr = #excerpted_helpers_path ::excerpted_pretty_stringify!($source);
::std::eprintln!("[{path}:{line}:{col}] {e:?}\n{expr}");
})
};
Method::try_from(res).expect(UNEXPECTED_ERROR_MESSAGE)
}
#[derive(Debug)]
pub struct FlavorStore {
flavors: HashMap<String, Flavor>,
}
// NOTE:
// 本当はパース後のFlavorsをグローバルに保持したいが、OnceLockはSync制約があるため
// TokenStreamを使っているFlavorsはそのままでは保持できない
// そのため変換確認だけ行った状態でHooqTomlをグローバルに保持し、
// 実際にFlavorsが必要な時にはunwrapを許可することにした
#[derive(Debug, Clone)]
pub struct CheckedHooqToml {
pub inner: HooqToml,
}
pub struct TomlStore {
inner: Mutex<HashMap<String, CheckedHooqToml>>,
}
pub static LOADED_HOOQ_TOML: LazyLock<TomlStore> = LazyLock::new(|| TomlStore {
inner: Mutex::new(HashMap::new()),
});
impl TomlStore {
fn load() -> Result<Option<CheckedHooqToml>, String> {
if let Some(checked_hooq_toml) = LOADED_HOOQ_TOML.get() {
return Ok(Some(checked_hooq_toml));
}
let dir_path = std::env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| ".".to_string());
let path = PathBuf::from(dir_path).join("hooq.toml");
if let Ok(false) | Err(_) = path.try_exists() {
return Ok(None);
}
let content = std::fs::read_to_string(&path)
.map_err(|e| format!("failed to read file `{}`: {}", path.display(), e))?;
let hooq_toml: HooqToml = toml::from_str(&content)
.map_err(|e| format!("failed to parse toml from file `{}`: {}", path.display(), e))?;
let checked_hooq_toml = CheckedHooqToml::try_from(hooq_toml)?;
LOADED_HOOQ_TOML.set(checked_hooq_toml.clone());
Ok(Some(checked_hooq_toml))
}
// NOTE:
// 異なるCargoプロジェクトを同じタイミングでVSCodeで開いていた時に
// 違うTomlStoreの内容が違うプロジェクトに供給されている事象が見られた
//
// 効果がどれぐらいあるかは懐疑的であるが、少しでも軽減すべく
// プロジェクトごとに保存領域を分けるようにした
fn set(&self, checked_hooq_toml: CheckedHooqToml) {
let key = std::env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| ".".to_string());
self.inner.lock().unwrap().insert(key, checked_hooq_toml);
}
fn get(&self) -> Option<CheckedHooqToml> {
let key = std::env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| ".".to_string());
self.inner.lock().unwrap().get(&key).cloned()
}
}
impl FlavorStore {
fn new() -> Self {
let flavors = presets::preset_flavors();
Self { flavors }
}
pub fn with_hooq_toml() -> Result<Self, String> {
let mut flavors = Self::new();
if let Some(hooq_toml) = TomlStore::load()? {
// 変換が成功することはCheckedHooqTomlの生成時に確認済み
toml_load::apply::update_flavors(&mut flavors.flavors, hooq_toml.inner).unwrap();
}
Ok(flavors)
}
fn get_flavor_inner(&self, path: &FlavorPath) -> Option<Flavor> {
let mut path = path.iter();
let mut current: &Flavor = self.flavors.get(path.next()?)?;
for name in path {
current = current.sub_flavors.get(name)?;
}
Some(current.clone())
}
pub fn get_flavor(&self, path: &FlavorPath) -> Result<Flavor, String> {
self.get_flavor_inner(path).ok_or_else(|| {
format!(
"flavor `{}` is not found. available flavors:
{}",
path.join("::"),
self.all_flavor_names()
.into_iter()
.map(|name| format!(" - {name}"))
.collect::<Vec<_>>()
.join("\n")
)
})
}
pub fn all_flavor_names(&self) -> Vec<String> {
fn collect_names(flavor: &Flavor, prefix: &str, names: &mut Vec<String>) {
let current_name = format!("{prefix}::");
for (sub_name, sub_flavor) in &flavor.sub_flavors {
let full_name = format!("{current_name}{sub_name}");
names.push(full_name.clone());
collect_names(sub_flavor, &full_name, names);
}
}
let mut names = Vec::new();
for (name, flavor) in &self.flavors {
names.push(name.clone());
collect_names(flavor, name, &mut names);
}
names
}
}
impl TryFrom<HooqToml> for FlavorStore {
type Error = String;
fn try_from(value: HooqToml) -> Result<Self, Self::Error> {
let mut flavors = Self::new();
toml_load::apply::update_flavors(&mut flavors.flavors, value)?;
Ok(flavors)
}
}
impl TryFrom<HooqToml> for CheckedHooqToml {
type Error = String;
// ロード時に変換可能かをあらかじめ確認する
fn try_from(value: HooqToml) -> Result<Self, Self::Error> {
let _ = FlavorStore::try_from(value.clone())?;
Ok(Self { inner: value })
}
}
Default method (Please ignore Japanese comments):
use std::collections::HashMap;
use std::path::PathBuf;
use std::rc::Rc;
use std::sync::{LazyLock, Mutex};
use proc_macro2::TokenStream;
use syn::{Expr, Path, parse_quote};
pub use crate::impls::flavor::flavor_path::FlavorPath;
use crate::impls::flavor::toml_load::HooqToml;
use crate::impls::inert_attr::context::HookTargetSwitch;
use crate::impls::method::Method;
use crate::impls::utils::unexpected_error_message::UNEXPECTED_ERROR_MESSAGE;
mod flavor_path;
mod presets;
mod toml_load;
#[derive(Debug, Clone)]
pub struct Flavor {
pub trait_uses: Vec<Path>,
pub method: Method,
pub hook_targets: HookTargetSwitch,
pub tail_expr_idents: Vec<String>,
pub ignore_tail_expr_idents: Vec<String>,
pub result_types: Vec<String>,
pub hook_in_macros: bool,
pub bindings: HashMap<String, Rc<Expr>>,
pub sub_flavors: HashMap<String, Flavor>,
}
impl Default for Flavor {
fn default() -> Self {
Self {
trait_uses: Vec::new(),
method: default_method(),
hook_targets: HookTargetSwitch {
question: true,
return_: true,
tail_expr: true,
},
tail_expr_idents: vec!["Err".to_string()],
ignore_tail_expr_idents: vec!["Ok".to_string()],
result_types: vec!["Result".to_string()],
hook_in_macros: true,
bindings: HashMap::new(),
sub_flavors: HashMap::new(),
}
}
}
fn default_method() -> Method {
// NOTE:
// $path や $line は eprintln! に直接埋め込みたいところだが、
// CI側のテストの関係でこのようになっている
let excerpted_helpers_path = crate::impls::utils::get_source_excerpt_helpers_name_space();
let res: TokenStream = parse_quote! {
.inspect_err(|e| {
let path = $path;
let line = $line;
let col = $col;
let expr = #excerpted_helpers_path ::excerpted_pretty_stringify!($source);
::std::eprintln!("[{path}:{line}:{col}] {e:?}\n{expr}");
})
};
Method::try_from(res).expect(UNEXPECTED_ERROR_MESSAGE)
}
#[derive(Debug)]
pub struct FlavorStore {
flavors: HashMap<String, Flavor>,
}
// NOTE:
// 本当はパース後のFlavorsをグローバルに保持したいが、OnceLockはSync制約があるため
// TokenStreamを使っているFlavorsはそのままでは保持できない
// そのため変換確認だけ行った状態でHooqTomlをグローバルに保持し、
// 実際にFlavorsが必要な時にはunwrapを許可することにした
#[derive(Debug, Clone)]
pub struct CheckedHooqToml {
pub inner: HooqToml,
}
pub struct TomlStore {
inner: Mutex<HashMap<String, CheckedHooqToml>>,
}
pub static LOADED_HOOQ_TOML: LazyLock<TomlStore> = LazyLock::new(|| TomlStore {
inner: Mutex::new(HashMap::new()),
});
impl TomlStore {
fn load() -> Result<Option<CheckedHooqToml>, String> {
if let Some(checked_hooq_toml) = LOADED_HOOQ_TOML.get() {
return Ok(Some(checked_hooq_toml));
}
let dir_path = std::env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| ".".to_string());
let path = PathBuf::from(dir_path).join("hooq.toml");
if let Ok(false) | Err(_) = path.try_exists() {
return Ok(None);
}
let content = std::fs::read_to_string(&path)
.map_err(|e| format!("failed to read file `{}`: {}", path.display(), e))?;
let hooq_toml: HooqToml = toml::from_str(&content)
.map_err(|e| format!("failed to parse toml from file `{}`: {}", path.display(), e))?;
let checked_hooq_toml = CheckedHooqToml::try_from(hooq_toml)?;
LOADED_HOOQ_TOML.set(checked_hooq_toml.clone());
Ok(Some(checked_hooq_toml))
}
// NOTE:
// 異なるCargoプロジェクトを同じタイミングでVSCodeで開いていた時に
// 違うTomlStoreの内容が違うプロジェクトに供給されている事象が見られた
//
// 効果がどれぐらいあるかは懐疑的であるが、少しでも軽減すべく
// プロジェクトごとに保存領域を分けるようにした
fn set(&self, checked_hooq_toml: CheckedHooqToml) {
let key = std::env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| ".".to_string());
self.inner.lock().unwrap().insert(key, checked_hooq_toml);
}
fn get(&self) -> Option<CheckedHooqToml> {
let key = std::env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| ".".to_string());
self.inner.lock().unwrap().get(&key).cloned()
}
}
impl FlavorStore {
fn new() -> Self {
let flavors = presets::preset_flavors();
Self { flavors }
}
pub fn with_hooq_toml() -> Result<Self, String> {
let mut flavors = Self::new();
if let Some(hooq_toml) = TomlStore::load()? {
// 変換が成功することはCheckedHooqTomlの生成時に確認済み
toml_load::apply::update_flavors(&mut flavors.flavors, hooq_toml.inner).unwrap();
}
Ok(flavors)
}
fn get_flavor_inner(&self, path: &FlavorPath) -> Option<Flavor> {
let mut path = path.iter();
let mut current: &Flavor = self.flavors.get(path.next()?)?;
for name in path {
current = current.sub_flavors.get(name)?;
}
Some(current.clone())
}
pub fn get_flavor(&self, path: &FlavorPath) -> Result<Flavor, String> {
self.get_flavor_inner(path).ok_or_else(|| {
format!(
"flavor `{}` is not found. available flavors:
{}",
path.join("::"),
self.all_flavor_names()
.into_iter()
.map(|name| format!(" - {name}"))
.collect::<Vec<_>>()
.join("\n")
)
})
}
pub fn all_flavor_names(&self) -> Vec<String> {
fn collect_names(flavor: &Flavor, prefix: &str, names: &mut Vec<String>) {
let current_name = format!("{prefix}::");
for (sub_name, sub_flavor) in &flavor.sub_flavors {
let full_name = format!("{current_name}{sub_name}");
names.push(full_name.clone());
collect_names(sub_flavor, &full_name, names);
}
}
let mut names = Vec::new();
for (name, flavor) in &self.flavors {
names.push(name.clone());
collect_names(flavor, name, &mut names);
}
names
}
}
impl TryFrom<HooqToml> for FlavorStore {
type Error = String;
fn try_from(value: HooqToml) -> Result<Self, Self::Error> {
let mut flavors = Self::new();
toml_load::apply::update_flavors(&mut flavors.flavors, value)?;
Ok(flavors)
}
}
impl TryFrom<HooqToml> for CheckedHooqToml {
type Error = String;
// ロード時に変換可能かをあらかじめ確認する
fn try_from(value: HooqToml) -> Result<Self, Self::Error> {
let _ = FlavorStore::try_from(value.clone())?;
Ok(Self { inner: value })
}
}
Usage:
use hooq::hooq;
#[hooq]
fn main() -> Result<(), Box<dyn std::error::Error>> {
Err("Hello, world!".into())
}
Result:
[mdbook-source-code/flavor-default/src/main.rs:5:5] "Hello, world!"
5> Err("Hell..rld!".into())
|
Error: "Hello, world!"
You can override via hooq.toml.
empty
Disables hooking; intended for conditional builds like #[cfg_attr(feature = "...", hooq(empty))].
use std::collections::HashMap;
use proc_macro2::TokenStream;
use syn::parse_quote;
use crate::impls::flavor::Flavor;
use crate::impls::inert_attr::context::HookTargetSwitch;
use crate::impls::utils::unexpected_error_message::UNEXPECTED_ERROR_MESSAGE;
pub fn empty_flavor() -> Flavor {
Flavor {
trait_uses: Vec::new(),
method: empty_method().try_into().expect(UNEXPECTED_ERROR_MESSAGE),
hook_targets: HookTargetSwitch {
question: false,
return_: false,
tail_expr: false,
},
tail_expr_idents: Vec::new(),
ignore_tail_expr_idents: Vec::new(),
result_types: Vec::new(),
hook_in_macros: false,
bindings: HashMap::new(),
sub_flavors: HashMap::new(),
}
}
fn empty_method() -> TokenStream {
parse_quote! {
$expr
}
}
Not overridable.
hook
(Please ignore Japanese comments)
use proc_macro2::TokenStream;
use syn::parse_quote;
use crate::impls::flavor::Flavor;
use crate::impls::utils::unexpected_error_message::UNEXPECTED_ERROR_MESSAGE;
pub fn hook_flavor() -> Flavor {
Flavor {
// NOTE: Traitの存在を前提とするflavorだがユーザーが決定する必要あり
// trait_uses: Vec::new(), // Default と同じ
method: hook_method().try_into().expect(UNEXPECTED_ERROR_MESSAGE),
..Default::default()
}
}
fn hook_method() -> TokenStream {
parse_quote! {
.hook(|| {
$hooq_meta
})
}
}
Designed for user traits to implement a hook method. It is useful when you do not want to use hooq.toml.
Usage:
use hooq::hooq;
mod my_error {
pub trait MyHook {
fn hook(self, meta_fn: impl FnOnce() -> hooq::HooqMeta) -> Self;
}
impl<T, E> MyHook for Result<T, E>
where
E: std::fmt::Debug,
{
fn hook(self, meta_fn: impl FnOnce() -> hooq::HooqMeta) -> Self {
if let Err(e) = &self {
let meta = meta_fn();
eprintln!(
"[{}:{}:{}] error occurred: {:?}",
meta.file, meta.line, meta.column, e
);
}
self
}
}
}
fn failable<T>(val: T) -> Result<T, String> {
Ok(val)
}
#[hooq(hook, trait_uses(my_error::MyHook))]
fn main() -> Result<(), Box<dyn std::error::Error>> {
failable(())?;
Ok(())
}
The second argument is a closure (meta_fn) for lazy evaluation to avoid constructing HooqMeta everywhere.
Expansion:
#![feature(prelude_import)]
#[macro_use]
extern crate std;
#[prelude_import]
use std::prelude::rust_2024::*;
use hooq::hooq;
mod my_error {
pub trait MyHook {
fn hook(self, meta_fn: impl FnOnce() -> hooq::HooqMeta) -> Self;
}
impl<T, E> MyHook for Result<T, E>
where
E: std::fmt::Debug,
{
fn hook(self, meta_fn: impl FnOnce() -> hooq::HooqMeta) -> Self {
if let Err(e) = &self {
let meta = meta_fn();
{
::std::io::_eprint(
format_args!(
"[{0}:{1}:{2}] error occurred: {3:?}\n",
meta.file,
meta.line,
meta.column,
e,
),
);
};
}
self
}
}
}
fn failable<T>(val: T) -> Result<T, String> {
Ok(val)
}
#[allow(unused)]
use my_error::MyHook as _;
fn main() -> Result<(), Box<dyn std::error::Error>> {
failable(())
.hook(|| {
::hooq::HooqMeta {
line: 33usize,
column: 17usize,
path: "mdbook-source-code/flavor-hook/src/main.rs",
file: "main.rs",
source_str: "failable(()) ?",
count: "1st ?",
bindings: ::std::collections::HashMap::from([]),
}
})?;
Ok(())
}
anyhow
Requires
anyhowfeature (included in default).
This flavor is intended to be used with the anyhow crate.
use proc_macro2::TokenStream;
use syn::parse_quote;
use crate::impls::flavor::Flavor;
use crate::impls::utils::unexpected_error_message::UNEXPECTED_ERROR_MESSAGE;
pub fn anyhow_flavor() -> Flavor {
Flavor {
trait_uses: vec![parse_quote! { ::anyhow::Context }],
method: anyhow_method().try_into().expect(UNEXPECTED_ERROR_MESSAGE),
..Default::default()
}
}
fn anyhow_method() -> TokenStream {
let excerpted_helpers_path = crate::impls::utils::get_source_excerpt_helpers_name_space();
parse_quote! {
.with_context(|| {
let path = $path;
let line = $line;
let col = $col;
let expr = #excerpted_helpers_path ::excerpted_pretty_stringify!($source);
format!("[{path}:{line}:{col}]\n{expr}")
})
}
}
Imports anyhow::Context for .with_context(...).
Usage:
use hooq::hooq;
#[hooq(anyhow)]
fn func1() -> anyhow::Result<i32> {
Err(anyhow::anyhow!("Error in func1"))
}
#[hooq(anyhow)]
fn func2() -> anyhow::Result<i32> {
let res = func1()?;
println!("{res}");
Ok(res)
}
#[hooq(anyhow)]
fn main() -> anyhow::Result<()> {
func2()?;
Ok(())
}
Result:
Error: [mdbook-source-code/flavor-anyhow/src/main.rs:19:12]
19> func2()?
|
Caused by:
0: [mdbook-source-code/flavor-anyhow/src/main.rs:10:22]
10> func1()?
|
1: [mdbook-source-code/flavor-anyhow/src/main.rs:5:5]
5> Err(anyhow::anyhow!("Error in func1"))
|
2: Error in func1
eyre
Requires
eyrefeature (included in default).
This flavor is intended to be used with the eyre crate.
use proc_macro2::TokenStream;
use syn::parse_quote;
use crate::impls::flavor::Flavor;
use crate::impls::utils::unexpected_error_message::UNEXPECTED_ERROR_MESSAGE;
pub fn eyre_flavor() -> Flavor {
Flavor {
trait_uses: vec![parse_quote! { ::eyre::WrapErr }],
method: eyre_method().try_into().expect(UNEXPECTED_ERROR_MESSAGE),
..Default::default()
}
}
fn eyre_method() -> TokenStream {
let excerpted_helpers_path = crate::impls::utils::get_source_excerpt_helpers_name_space();
parse_quote! {
.wrap_err_with(|| {
let path = $path;
let line = $line;
let col = $col;
let expr = #excerpted_helpers_path ::excerpted_pretty_stringify!($source);
format!("[{path}:{line}:{col}]\n{expr}")
})
}
}
Imports eyre::WrapErr for .wrap_err_with(...).
Usage:
use hooq::hooq;
#[hooq(eyre)]
fn func1() -> eyre::Result<i32> {
Err(eyre::eyre!("Error in func1"))
}
#[hooq(eyre)]
fn func2() -> eyre::Result<i32> {
let res = func1()?;
println!("{res}");
Ok(res)
}
#[hooq(eyre)]
fn main() -> eyre::Result<()> {
func2()?;
Ok(())
}
Result:
Error: [mdbook-source-code/flavor-eyre/src/main.rs:19:12]
19> func2()?
|
Caused by:
0: [mdbook-source-code/flavor-eyre/src/main.rs:10:22]
10> func1()?
|
1: [mdbook-source-code/flavor-eyre/src/main.rs:5:5]
5> Err(eyre::eyre!("Error in func1"))
|
2: Error in func1
Location:
mdbook-source-code/flavor-eyre/src/main.rs:5:9
log
Requires
logfeature (included in default).
This flavor is intended to be used with the log crate.
use proc_macro2::TokenStream;
use syn::parse_quote;
use crate::impls::flavor::Flavor;
use crate::impls::utils::unexpected_error_message::UNEXPECTED_ERROR_MESSAGE;
pub fn log_flavor() -> Flavor {
Flavor {
method: log_method().try_into().expect(UNEXPECTED_ERROR_MESSAGE),
..Default::default()
}
}
fn log_method() -> TokenStream {
let excerpted_helpers_path = crate::impls::utils::get_source_excerpt_helpers_name_space();
parse_quote! {
.inspect_err(|e| {
let path = $path;
let line = $line;
let col = $col;
let expr = #excerpted_helpers_path ::excerpted_pretty_stringify!($source);
::log::error!("({path}:{line}:{col}) {e}\n{expr}");
})
}
}
Usage:
use hooq::hooq;
#[hooq(log)]
fn main() -> Result<(), Box<dyn std::error::Error>> {
env_logger::Builder::new()
.format_timestamp(None)
.filter_level(log::LevelFilter::Error)
.init();
Err("Hello, world!".into())
}
Result:
[ERROR flavor_log] (mdbook-source-code/flavor-log/src/main.rs:10:5) Hello, world!
10> Err("Hell..rld!".into())
|
Error: "Hello, world!"
tracing
Requires
tracingfeature (included in default).
This flavor is intended to be used with the tracing crate.
use proc_macro2::TokenStream;
use syn::parse_quote;
use crate::impls::flavor::Flavor;
use crate::impls::utils::unexpected_error_message::UNEXPECTED_ERROR_MESSAGE;
pub fn tracing_flavor() -> Flavor {
Flavor {
method: tracing_method().try_into().expect(UNEXPECTED_ERROR_MESSAGE),
..Default::default()
}
}
fn tracing_method() -> TokenStream {
let excerpted_helpers_path = crate::impls::utils::get_source_excerpt_helpers_name_space();
parse_quote! {
.inspect_err(|e| {
let path = $path;
let line = $line;
let col = $col;
let expr = #excerpted_helpers_path ::one_line_stringify!($source);
::tracing::error!(
path,
line,
col,
error = %e,
expr,
);
})
}
}
Place #[hooq(tracing)] above #[tracing::instrument] to ensure order.
Usage:
use hooq::hooq;
use tracing::instrument;
#[hooq(tracing)]
#[instrument]
fn func1() -> Result<i32, String> {
Err("Error in func1".into())
}
#[hooq(tracing)]
#[instrument]
fn func2() -> Result<i32, String> {
println!("func2 start");
let res = func1()?;
println!("func2 end: {res}");
Ok(res)
}
#[hooq(tracing)]
#[instrument]
fn func3() -> Result<i32, String> {
println!("func3 start");
let res = func2()?;
println!("func3 end: {res}");
Ok(res)
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let format = tracing_subscriber::fmt::format()
.with_ansi(false)
.without_time();
tracing_subscriber::fmt().event_format(format).init();
func3()?;
Ok(())
}
Result:
func3 start
func2 start
ERROR func3:func2:func1: flavor_tracing: path="mdbook-source-code/flavor-tracing/src/main.rs" line=7 col=5 error=Error in func1 expr="Err(\"Error in func1\".into())"
ERROR func3:func2: flavor_tracing: path="mdbook-source-code/flavor-tracing/src/main.rs" line=15 col=22 error=Error in func1 expr="func1()?"
ERROR func3: flavor_tracing: path="mdbook-source-code/flavor-tracing/src/main.rs" line=27 col=22 error=Error in func1 expr="func2()?"
Error: "Error in func1"