1use std::{
8 fs::{File, OpenOptions},
9 io::{self, Write},
10 sync::{Mutex, OnceLock},
11 time::{SystemTime, UNIX_EPOCH},
12};
13
14use bedrock_assertions::Assertion;
15use serde::Serialize;
16
17#[doc(hidden)]
18pub use bedrock_assertions as __bedrock;
19#[doc(hidden)]
20pub use serde_json as __serde_json;
21
22static OUTPUT: OnceLock<io::Result<Option<Mutex<File>>>> = OnceLock::new();
23
24#[doc(hidden)]
28pub fn __report(assertion: Assertion, details: impl Serialize) {
29 let result = (|| {
30 let output = OUTPUT.get_or_init(|| {
31 std::env::var_os("BEDROCK_ASSERTIONS_PATH")
32 .map(|path| OpenOptions::new().create(true).append(true).open(path))
33 .transpose()
34 .map(|file| file.map(Mutex::new))
35 });
36 let Some(output) =
37 output.as_ref().map_err(|error| io::Error::new(error.kind(), error.to_string()))?
38 else {
39 return Ok(());
40 };
41
42 let mut record = serde_json::to_value(&assertion).map_err(io::Error::other)?;
43 let details = serde_json::to_value(details).map_err(io::Error::other)?;
44 let kind = match assertion {
45 Assertion::Always(_) => "Always",
46 Assertion::Sometimes(_) => "Sometimes",
47 };
48 let data = record[kind].as_object_mut().expect("Bedrock assertion has an object payload");
49 data.insert(
50 "timestamp_unix_nano".into(),
51 (SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().as_nanos() as u64)
52 .into(),
53 );
54 if !details.is_null() {
55 data.insert("details".into(), details);
56 }
57 let mut line = serde_json::to_vec(&record).map_err(io::Error::other)?;
58 line.push(b'\n');
59 if line.len() > 16 * 1024 {
60 return Err(io::Error::new(io::ErrorKind::InvalidInput, "DST record exceeds 16 KiB"));
61 }
62 let mut file = output.lock().map_err(|_| io::Error::other("DST writer poisoned"))?;
63 if file.write(&line)? != line.len() {
65 return Err(io::Error::new(io::ErrorKind::WriteZero, "partial DST write"));
66 }
67 Ok::<_, io::Error>(())
68 })();
69 if let Err(error) = result {
70 panic!("reth-dst could not report assertion {:?}: {error}", assertion.data().message);
71 }
72}
73
74#[macro_export]
76macro_rules! always {
77 ($condition:expr, $name:expr $(, $details:expr)? $(,)?) => {{
78 $crate::__assert!($condition, $name, always $(, $details)?);
79 }};
80}
81
82#[macro_export]
85macro_rules! sometimes {
86 ($condition:expr, $name:expr $(, $details:expr)? $(,)?) => {{
87 $crate::__assert!($condition, $name, sometimes $(, $details)?);
88 }};
89}
90
91#[doc(hidden)]
92#[macro_export]
93macro_rules! __assert {
94 ($condition:expr, $name:expr, $kind:ident $(,)?) => {{
95 $crate::__assert!($condition, $name, $kind, $crate::__serde_json::Value::Null);
96 }};
97 ($condition:expr, $name:expr, $kind:ident, $details:expr $(,)?) => {{
98 const NAME: &str = $name;
99 const _: () = assert!(!NAME.is_empty(), "assertion name must not be empty");
100 let condition: bool = $condition;
101 let details = $details;
102 $crate::__report(
103 $crate::__bedrock::Assertion::$kind(
104 $crate::__bedrock::Condition::Bool(condition),
105 NAME,
106 $crate::__bedrock::Location::new(file!(), line!(), column!()),
107 ),
108 details,
109 );
110 }};
111}