Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/rust/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ Functions returning `Result<T>` 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

Expand Down
6 changes: 6 additions & 0 deletions src/rust/kj/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T> = std::result::Result<T, cxx::KjError>;
150 changes: 150 additions & 0 deletions src/rust/kj/macros.rs
Original file line number Diff line number Diff line change
@@ -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<u32> {
/// 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<T>` (or any `Result<T, E>` where `E: From<cxx::KjError>`).
///
/// # Examples
///
/// ```ignore
/// use kj::fail_require;
///
/// fn require_positive(n: i32) -> kj::Result<u32> {
/// 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())
};
}
6 changes: 1 addition & 5 deletions src/rust/worker/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}

Expand Down
10 changes: 3 additions & 7 deletions src/rust/worker/kill_switch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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))
}
}

Expand Down
6 changes: 1 addition & 5 deletions src/rust/worker/ok.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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")
}
}

Expand Down
Loading