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 Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ members = ["crates/aerro", "crates/aerro-macros"]
# (Reserved for workspace-wide clippy overrides; currently none.)

[workspace.package]
version = "0.6.4"
version = "0.7.0"
edition = "2024"
rust-version = "1.85"
license = "MIT OR Apache-2.0"
Expand Down
2 changes: 1 addition & 1 deletion crates/aerro/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ macro = ["dep:aerro-macros"]
tracing = ["dep:tracing", "dep:tracing-opentelemetry", "dep:opentelemetry"]

[dependencies]
aerro-macros = { path = "../aerro-macros", version = "0.6.2", optional = true }
aerro-macros = { path = "../aerro-macros", version = "0.7.0", optional = true }
tonic = { workspace = true }
prost = { workspace = true }
bytes = { workspace = true }
Expand Down
10 changes: 5 additions & 5 deletions crates/aerro/examples/trace_chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ fn middleware() -> Result<(), tonic::Status> {
Ok(v) => Ok(v),
Err(e) => {
let mut sf: ServiceFailure<Pipeline> = e.into();
sf.frames.push(Frame::local(
sf.frames_mut().push(Frame::local(
"backend",
"ping",
Code::Internal,
Expand All @@ -42,7 +42,7 @@ fn gateway() -> Result<(), tonic::Status> {
Ok(v) => Ok(v),
Err(st) => {
let mut sf = st.into_aerro::<Pipeline>().expect("typed");
sf.frames.push(Frame::local(
sf.frames_mut().push(Frame::local(
"middleware",
"forward",
Code::Internal,
Expand All @@ -58,19 +58,19 @@ fn main() {
let st = gateway().unwrap_err();
let sf = st.into_aerro::<Pipeline>().expect("typed at gateway");
let mut sf = sf;
sf.frames.push(Frame::local(
sf.frames_mut().push(Frame::local(
"gateway",
"handle",
Code::Internal,
"client-side",
Category::System,
));
println!("frames on final hop:");
for (i, f) in sf.frames.iter().enumerate() {
for (i, f) in sf.frames().iter().enumerate() {
println!(
" {i}: service={} rpc={} code={:?} msg={}",
f.service, f.rpc, f.code, f.message
);
}
println!("trace_id={:02x?}", sf.trace.trace_id);
println!("trace_id={:02x?}", sf.trace().trace_id);
}
2 changes: 1 addition & 1 deletion crates/aerro/src/ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,6 @@ mod tests {
fn status_into_aerro_recovers() {
let st = Boom { x: 2 }.into_status(&EncodeOptions::default());
let sf = st.into_aerro::<Boom>().unwrap();
assert_eq!(sf.inner.x, 2);
assert_eq!(sf.inner().x, 2);
}
}
44 changes: 26 additions & 18 deletions crates/aerro/src/failure.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,11 @@ use smallvec::SmallVec;

use crate::{Aerro, Frame, trace::TraceContext};

/// Boxed state of [`ServiceFailure`]. Exposed only so that field access
/// through `Deref`/`DerefMut` (`sf.inner`, `sf.frames`, `sf.trace`) names a
/// public type; you almost never write this name directly.
#[derive(Debug)]
pub struct ServiceFailureInner<E: Aerro> {
pub inner: E,
pub frames: SmallVec<[Frame; 4]>,
pub trace: TraceContext,
pub(crate) struct ServiceFailureInner<E: Aerro> {
pub(crate) inner: E,
pub(crate) frames: SmallVec<[Frame; 4]>,
pub(crate) trace: TraceContext,
}

#[derive(Debug)]
Expand Down Expand Up @@ -64,18 +61,29 @@ impl<E: Aerro> ServiceFailure<E> {
} = *self.state;
(inner, frames, trace)
}
}

impl<E: Aerro> std::ops::Deref for ServiceFailure<E> {
type Target = ServiceFailureInner<E>;
fn deref(&self) -> &ServiceFailureInner<E> {
&self.state
pub fn inner(&self) -> &E {
&self.state.inner
}

pub fn inner_mut(&mut self) -> &mut E {
&mut self.state.inner
}

pub fn frames(&self) -> &SmallVec<[Frame; 4]> {
&self.state.frames
}

pub fn frames_mut(&mut self) -> &mut SmallVec<[Frame; 4]> {
&mut self.state.frames
}

pub fn trace(&self) -> &TraceContext {
&self.state.trace
}
}

impl<E: Aerro> std::ops::DerefMut for ServiceFailure<E> {
fn deref_mut(&mut self) -> &mut ServiceFailureInner<E> {
&mut self.state
pub fn trace_mut(&mut self) -> &mut TraceContext {
&mut self.state.trace
}
}

Expand Down Expand Up @@ -105,8 +113,8 @@ mod tests {
#[test]
fn from_e_constructs_failure_with_empty_frames() {
let sf: ServiceFailure<Boom> = Boom { x: 1 }.into();
assert!(sf.frames.is_empty());
assert!(sf.trace.is_empty());
assert!(sf.frames().is_empty());
assert!(sf.trace().is_empty());
}

#[test]
Expand Down
84 changes: 32 additions & 52 deletions crates/aerro/src/remote.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,28 +9,14 @@ use tonic::Code;

use crate::{Aerro, Category, Frame, trace::TraceContext};

/// Boxed state of [`RemoteError`]. Exposed only so field access through
/// `Deref`/`DerefMut` (`e.type_id`, `e.frames`, ...) names a public type.
#[derive(Debug)]
pub struct RemoteErrorInner {
pub category: Category,
pub type_id: String,
pub frames: SmallVec<[Frame; 4]>,
pub trace: TraceContext,
pub outer_code: Code,
pub outer_message: String,
pub(crate) payload_bytes: Bytes,
}

/// Components used to construct a [`RemoteError`] via [`RemoteError::from_parts`].
#[derive(Debug)]
pub struct RemoteErrorParts {
pub category: Category,
pub type_id: String,
pub frames: SmallVec<[Frame; 4]>,
pub trace: TraceContext,
pub outer_code: Code,
pub outer_message: String,
pub(crate) category: Category,
pub(crate) type_id: String,
pub(crate) frames: SmallVec<[Frame; 4]>,
pub(crate) trace: TraceContext,
pub(crate) outer_code: Code,
pub(crate) outer_message: String,
pub(crate) payload_bytes: Bytes,
}

Expand All @@ -40,26 +26,9 @@ pub struct RemoteError {
}

impl RemoteError {
pub fn from_parts(parts: RemoteErrorParts) -> Self {
let RemoteErrorParts {
category,
type_id,
frames,
trace,
outer_code,
outer_message,
payload_bytes,
} = parts;
pub fn from_parts(inner: RemoteErrorInner) -> Self {
Self {
state: Box::new(RemoteErrorInner {
category,
type_id,
frames,
trace,
outer_code,
outer_message,
payload_bytes,
}),
state: Box::new(inner),
}
}

Expand All @@ -70,18 +39,29 @@ impl RemoteError {
}
E::decode_payload(&self.state.type_id, &self.state.payload_bytes).ok()
}
}

impl std::ops::Deref for RemoteError {
type Target = RemoteErrorInner;
fn deref(&self) -> &RemoteErrorInner {
&self.state
pub fn category(&self) -> Category {
self.state.category
}

pub fn type_id(&self) -> &str {
&self.state.type_id
}

pub fn frames(&self) -> &SmallVec<[Frame; 4]> {
&self.state.frames
}

pub fn trace(&self) -> &TraceContext {
&self.state.trace
}

pub fn outer_code(&self) -> Code {
self.state.outer_code
}
}

impl std::ops::DerefMut for RemoteError {
fn deref_mut(&mut self) -> &mut RemoteErrorInner {
&mut self.state
pub fn outer_message(&self) -> &str {
&self.state.outer_message
}
}

Expand All @@ -98,8 +78,8 @@ mod tests {
use super::*;
use crate::test_support::Boom;

fn make(parts: RemoteErrorParts) -> RemoteError {
RemoteError::from_parts(parts)
fn make(inner: RemoteErrorInner) -> RemoteError {
RemoteError::from_parts(inner)
}

#[test]
Expand All @@ -109,7 +89,7 @@ mod tests {
Boom { x: 7 }
.encode_payload(Exposure::Internal, &mut buf)
.unwrap();
let r = make(RemoteErrorParts {
let r = make(RemoteErrorInner {
category: Category::System,
type_id: "toy.boom".into(),
frames: SmallVec::new(),
Expand All @@ -123,7 +103,7 @@ mod tests {

#[test]
fn downcast_returns_none_on_mismatch() {
let r = make(RemoteErrorParts {
let r = make(RemoteErrorInner {
category: Category::Business,
type_id: "other".into(),
frames: SmallVec::new(),
Expand Down
12 changes: 6 additions & 6 deletions crates/aerro/src/wire/decode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ fn into_remote_error(env: raw::Envelope, status: &Status) -> RemoteError {
from_proto(raw::Category::try_from(env.category).unwrap_or(raw::Category::System));
let trace = decode_trace(&env.trace_id, &env.span_id);
let frames = decode_frames(&env.frames);
RemoteError::from_parts(crate::remote::RemoteErrorParts {
RemoteError::from_parts(crate::remote::RemoteErrorInner {
category,
type_id: env.type_id,
frames,
Expand All @@ -56,7 +56,7 @@ fn into_remote_error(env: raw::Envelope, status: &Status) -> RemoteError {
}

fn transport_remote_error(status: &Status) -> RemoteError {
RemoteError::from_parts(crate::remote::RemoteErrorParts {
RemoteError::from_parts(crate::remote::RemoteErrorInner {
category: Category::Transport,
type_id: "aerro.transport".into(),
frames: SmallVec::new(),
Expand Down Expand Up @@ -116,7 +116,7 @@ mod tests {
let sf: ServiceFailure<Boom> = Boom { x: 13 }.into();
let st = encode(&sf, &EncodeOptions::default());
let back = decode::<Boom>(st).expect("known type round-trips");
assert_eq!(back.inner.x, 13);
assert_eq!(back.inner().x, 13);
}

#[test]
Expand Down Expand Up @@ -150,15 +150,15 @@ mod tests {
}
}
let r = decode::<Other>(st).expect_err("type_id mismatch");
assert_eq!(r.type_id, "toy.boom");
assert_eq!(r.type_id(), "toy.boom");
assert_eq!(r.downcast::<Boom>().unwrap().x, 5);
}

#[test]
fn bare_status_becomes_transport_remote_error() {
let st = Status::unavailable("backend down");
let r = decode::<Boom>(st).err().unwrap();
assert_eq!(r.category, Category::Transport);
assert_eq!(r.outer_code, Code::Unavailable);
assert_eq!(r.category(), Category::Transport);
assert_eq!(r.outer_code(), Code::Unavailable);
}
}
20 changes: 10 additions & 10 deletions crates/aerro/src/wire/encode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,25 +36,25 @@ pub fn encode<E: Aerro>(sf: &ServiceFailure<E>, opts: &EncodeOptions) -> Status
// route is what governs what leaves the process.
let route = opts.exposure;

let outer_code = sf.inner.code();
let outer_msg = redact_message(&sf.inner, route);
let outer_code = sf.inner().code();
let outer_msg = redact_message(sf.inner(), route);

let mut payload = Vec::new();
if let Err(e) = sf.inner.encode_payload(route, &mut payload) {
if let Err(e) = sf.inner().encode_payload(route, &mut payload) {
return Status::new(Code::Internal, format!("aerro: encode failed: {e}"));
}

let wire_frames = if route == Exposure::Public {
Vec::new()
} else {
elide_to_cap(&sf.frames, opts.max_frames)
elide_to_cap(sf.frames(), opts.max_frames)
};

let env = raw::Envelope {
category: to_proto(sf.inner.category()) as i32,
type_id: Aerro::type_id(&sf.inner).to_string(),
trace_id: sf.trace.trace_id.to_vec(),
span_id: sf.trace.span_id.to_vec(),
category: to_proto(sf.inner().category()) as i32,
type_id: Aerro::type_id(sf.inner()).to_string(),
trace_id: sf.trace().trace_id.to_vec(),
span_id: sf.trace().span_id.to_vec(),
frames: wire_frames,
payload,
version: ENVELOPE_VERSION,
Expand Down Expand Up @@ -142,7 +142,7 @@ mod tests {
fn elision_keeps_cap() {
let mut sf: ServiceFailure<Boom> = Boom { x: 0 }.into();
for i in 0..20u32 {
sf.frames.push(Frame::local(
sf.frames_mut().push(Frame::local(
"svc",
"rpc",
Code::Internal,
Expand All @@ -165,7 +165,7 @@ mod tests {
#[test]
fn public_drops_frames() {
let mut sf: ServiceFailure<Boom> = Boom { x: 0 }.into();
sf.frames.push(Frame::local(
sf.frames_mut().push(Frame::local(
"svc",
"rpc",
Code::Internal,
Expand Down
2 changes: 1 addition & 1 deletion crates/aerro/tests/macro_basic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,5 +109,5 @@ fn tuple_variant_roundtrips_via_wire() {
fn unit_variant_roundtrips_via_wire() {
let st = CreateUser::Boom.into_status(&EncodeOptions::default());
let sf: ServiceFailure<CreateUser> = st.into_aerro::<CreateUser>().unwrap();
assert!(matches!(sf.inner, CreateUser::Boom));
assert!(matches!(sf.inner(), CreateUser::Boom));
}
Loading
Loading