diff --git a/src/rust/AGENTS.md b/src/rust/AGENTS.md index 769b8b1c3c7..3a13eb68b3d 100644 --- a/src/rust/AGENTS.md +++ b/src/rust/AGENTS.md @@ -85,7 +85,7 @@ Functions returning `Result` in `extern "Rust"` blocks translate to C++ funct - **`thiserror` enums**: Define a crate-level `Error` enum for structured errors. This is the preferred pattern for crates with multiple error cases. - **`std::io::Error`**: Acceptable for purely I/O-related errors. -- **`cxx::KjError`**: Use `KjError::new(KjExceptionType::Failed, message)` when you need direct control over the KJ exception type. +- **`cxx::KjError`**: Use the `kj` crate's error macros (`kj::failed!`, `kj::overloaded!`, `kj::disconnected!`, `kj::not_implemented!` to construct a value; `kj::fail_require!`, `kj::overloaded_require!`, `kj::disconnected_require!`, `kj::not_implemented_require!` to early-return `Err(...)`) when you need direct control over the KJ exception type. These mirror C++'s `KJ_EXCEPTION`/`KJ_FAIL_REQUIRE`/`KJ_UNIMPLEMENTED` macros from `kj/debug.h`; see `src/rust/kj/macros.rs`. ## CXX BRIDGE: BUILD WIRING diff --git a/src/rust/kj/lib.rs b/src/rust/kj/lib.rs index 718de43a2f3..85a1256a484 100644 --- a/src/rust/kj/lib.rs +++ b/src/rust/kj/lib.rs @@ -5,8 +5,14 @@ pub mod http; pub mod io; +pub mod macros; mod own; + +// Re-exported so that `$crate::KjError` / `$crate::KjExceptionType` resolve inside the +// macros in `macros.rs` regardless of whether the invoking crate itself depends on `cxx`. +pub use cxx::KjError; +pub use cxx::KjExceptionType; pub use own::*; pub type Result = std::result::Result; diff --git a/src/rust/kj/macros.rs b/src/rust/kj/macros.rs new file mode 100644 index 00000000000..c21be81440e --- /dev/null +++ b/src/rust/kj/macros.rs @@ -0,0 +1,150 @@ +// Copyright (c) 2026 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 + +//! Macros for building [`cxx::KjError`] values, mirroring the C++ `KJ_EXCEPTION`/`KJ_FAIL_REQUIRE`/ +//! `KJ_UNIMPLEMENTED` macros defined in `kj/debug.h`. +//! +//! Each of KJ's four [`cxx::KjExceptionType`] variants (`Failed`, `Overloaded`, `Disconnected`, +//! `Unimplemented`) gets two macros: +//! +//! - A construct-only macro (`failed!`, `overloaded!`, `disconnected!`, `not_implemented!`) that +//! evaluates to a [`cxx::KjError`] value, mirroring C++ `KJ_EXCEPTION(TYPE, ...)`. The caller +//! decides what to do with it (`Err(...)`, `.into()`, attach `.with_location(...)`, etc.). +//! - An early-return macro (`fail_require!`, `overloaded_require!`, `disconnected_require!`, +//! `not_implemented_require!`) that unconditionally returns `Err(...)` from the enclosing +//! function, mirroring C++ `KJ_FAIL_REQUIRE(...)` / `KJ_UNIMPLEMENTED(...)`. (`unimplemented!` +//! is avoided as a macro name here since it would shadow `std::unimplemented!` wherever +//! imported.) + +/// Builds a [`cxx::KjError`] with [`cxx::KjExceptionType::Failed`] and a `format!`-style message. +/// +/// This is the Rust equivalent of the C++ `KJ_EXCEPTION(FAILED, ...)` macro defined in +/// `kj/debug.h`. Unlike `KJ_FAIL_REQUIRE`, this only *constructs* the error; it does not return +/// or throw. Wrap the result in `Err(...)` (or use [`fail_require!`] for the early-return form). +/// +/// # Examples +/// +/// ```ignore +/// use kj::failed; +/// +/// fn parse(input: &str) -> kj::Result { +/// input.parse().map_err(|_| failed!("invalid number: '{}'", input)) +/// } +/// ``` +#[macro_export] +macro_rules! failed { + ($msg:literal $(, $arg:expr)* $(,)?) => { + $crate::KjError::new($crate::KjExceptionType::Failed, format!($msg $(, $arg)*)) + }; +} + +/// Builds a [`cxx::KjError`] with [`cxx::KjExceptionType::Overloaded`] and a `format!`-style +/// message. +/// +/// This is the Rust equivalent of the C++ `KJ_EXCEPTION(OVERLOADED, ...)` macro. Use this for +/// calls that failed because of a temporary lack of resources (retryable). See [`failed!`] for +/// general usage notes. +#[macro_export] +macro_rules! overloaded { + ($msg:literal $(, $arg:expr)* $(,)?) => { + $crate::KjError::new($crate::KjExceptionType::Overloaded, format!($msg $(, $arg)*)) + }; +} + +/// Builds a [`cxx::KjError`] with [`cxx::KjExceptionType::Disconnected`] and a `format!`-style +/// message. +/// +/// This is the Rust equivalent of the C++ `KJ_EXCEPTION(DISCONNECTED, ...)` macro. Use this when +/// the call required communication over a connection that has been lost. See [`failed!`] for +/// general usage notes. +#[macro_export] +macro_rules! disconnected { + ($msg:literal $(, $arg:expr)* $(,)?) => { + $crate::KjError::new($crate::KjExceptionType::Disconnected, format!($msg $(, $arg)*)) + }; +} + +/// Builds a [`cxx::KjError`] with [`cxx::KjExceptionType::Unimplemented`] and a `format!`-style +/// message. +/// +/// This is the Rust equivalent of the C++ `KJ_UNIMPLEMENTED(...)` / `KJ_EXCEPTION(UNIMPLEMENTED, +/// ...)` macros. Named `not_implemented!` rather than `unimplemented!` to avoid shadowing +/// `std::unimplemented!` at import sites. See [`failed!`] for general usage notes. +#[macro_export] +macro_rules! not_implemented { + ($msg:literal $(, $arg:expr)* $(,)?) => { + $crate::KjError::new($crate::KjExceptionType::Unimplemented, format!($msg $(, $arg)*)) + }; +} + +/// Unconditionally returns `Err(...)` from the enclosing function with a +/// [`cxx::KjExceptionType::Failed`] error. +/// +/// This is the Rust equivalent of the C++ `KJ_FAIL_REQUIRE(...)` macro. The enclosing function +/// must return `kj::Result` (or any `Result` where `E: From`). +/// +/// # Examples +/// +/// ```ignore +/// use kj::fail_require; +/// +/// fn require_positive(n: i32) -> kj::Result { +/// if n <= 0 { +/// fail_require!("expected a positive number, got {}", n); +/// } +/// Ok(n as u32) +/// } +/// ``` +#[macro_export] +macro_rules! fail_require { + ($msg:literal $(, $arg:expr)* $(,)?) => { + return Err($crate::failed!($msg $(, $arg)*).into()) + }; +} + +/// Unconditionally returns `Err(...)` from the enclosing function with a +/// [`cxx::KjExceptionType::Overloaded`] error. +/// +/// This is the Rust equivalent of the C++ `KJ_EXCEPTION(OVERLOADED, ...)` thrown via +/// `KJ_FAIL_REQUIRE`-style control flow. See [`fail_require!`] for general usage notes. +#[macro_export] +macro_rules! overloaded_require { + ($msg:literal $(, $arg:expr)* $(,)?) => { + return Err($crate::overloaded!($msg $(, $arg)*).into()) + }; +} + +/// Unconditionally returns `Err(...)` from the enclosing function with a +/// [`cxx::KjExceptionType::Disconnected`] error. +/// +/// This is the Rust equivalent of the C++ `KJ_EXCEPTION(DISCONNECTED, ...)` thrown via +/// `KJ_FAIL_REQUIRE`-style control flow. See [`fail_require!`] for general usage notes. +#[macro_export] +macro_rules! disconnected_require { + ($msg:literal $(, $arg:expr)* $(,)?) => { + return Err($crate::disconnected!($msg $(, $arg)*).into()) + }; +} + +/// Unconditionally returns `Err(...)` from the enclosing function with a +/// [`cxx::KjExceptionType::Unimplemented`] error. +/// +/// This is the Rust equivalent of the C++ `KJ_UNIMPLEMENTED(...)` macro. See [`fail_require!`] +/// for general usage notes. +/// +/// # Examples +/// +/// ```ignore +/// use kj::not_implemented_require; +/// +/// fn connect() -> kj::Result<()> { +/// not_implemented_require!("connect is not supported by this worker"); +/// } +/// ``` +#[macro_export] +macro_rules! not_implemented_require { + ($msg:literal $(, $arg:expr)* $(,)?) => { + return Err($crate::not_implemented!($msg $(, $arg)*).into()) + }; +} diff --git a/src/rust/worker/error.rs b/src/rust/worker/error.rs index 9cf153ebed9..fa2d9e7f6bf 100644 --- a/src/rust/worker/error.rs +++ b/src/rust/worker/error.rs @@ -43,11 +43,7 @@ impl Worker { } fn error(&self, file: &str, line: u32) -> KjError { - KjError::new( - cxx::KjExceptionType::Failed, - format!("jsg.Error: {}", self.message), - ) - .with_location(file.to_owned(), line) + kj::failed!("jsg.Error: {}", self.message).with_location(file.to_owned(), line) } } diff --git a/src/rust/worker/kill_switch.rs b/src/rust/worker/kill_switch.rs index df7d08e0202..a1009ec4941 100644 --- a/src/rust/worker/kill_switch.rs +++ b/src/rust/worker/kill_switch.rs @@ -5,7 +5,6 @@ use std::pin::Pin; use std::time::SystemTime; -use cxx::KjError; use kj::http::ConnectResponse; use kj::http::ConnectSettings; use kj::http::HeadersRef; @@ -42,12 +41,9 @@ pub struct Worker {} impl Worker { fn error(file: &str, line: u32) -> Result<()> { - Err(KjError::new( - cxx::KjExceptionType::Failed, - "jsg.Error: This script has been killed.".to_owned(), - ) - .with_details(vec![(SCRIPT_KILLED_DETAIL_ID, vec![])]) - .with_location(file.to_owned(), line)) + Err(kj::failed!("jsg.Error: This script has been killed.") + .with_details(vec![(SCRIPT_KILLED_DETAIL_ID, vec![])]) + .with_location(file.to_owned(), line)) } } diff --git a/src/rust/worker/ok.rs b/src/rust/worker/ok.rs index 82a8e1b8f0e..aabfad9978e 100644 --- a/src/rust/worker/ok.rs +++ b/src/rust/worker/ok.rs @@ -6,7 +6,6 @@ use std::pin::Pin; use std::time::SystemTime; use cxx::KjError; -use cxx::KjExceptionType; use kj::http::ConnectResponse; use kj::http::ConnectSettings; use kj::http::HeadersRef; @@ -38,10 +37,7 @@ pub struct Worker; impl Worker { fn not_implemented(name: &str) -> KjError { - KjError::new( - KjExceptionType::Unimplemented, - format!("{name} not implemented"), - ) + kj::not_implemented!("{name} not implemented") } }