diff --git a/server/crates/arbiter-terrors-poc/Cargo.toml b/server/crates/arbiter-terrors-poc/Cargo.toml deleted file mode 100644 index 127adb1..0000000 --- a/server/crates/arbiter-terrors-poc/Cargo.toml +++ /dev/null @@ -1,7 +0,0 @@ -[package] -name = "arbiter-terrors-poc" -version = "0.1.0" -edition = "2024" - -[dependencies] -terrors = "0.3" diff --git a/server/crates/arbiter-terrors-poc/src/auth.rs b/server/crates/arbiter-terrors-poc/src/auth.rs deleted file mode 100644 index b9f6148..0000000 --- a/server/crates/arbiter-terrors-poc/src/auth.rs +++ /dev/null @@ -1,139 +0,0 @@ -use crate::errors::{InternalError1, InternalError2, InvalidSignature, NotRegistered}; -use terrors::OneOf; - -use crate::errors::ProtoError; - -// Each sub-call's error type already implements DrainInto, so we convert -// directly to ProtoError without broaden — no turbofish needed anywhere. -// -// Call chain: -// load_config() → OneOf<(InternalError2,)> → ProtoError::from -// get_nonce() → OneOf<(InternalError1, InternalError2)> → ProtoError::from -// verify_sig() → OneOf<(InvalidSignature,)> → ProtoError::from -pub fn process_request(id: u32, sig: &str) -> Result { - if id == 0 { - return Err(ProtoError::NotRegistered); - } - - let config = load_config(id).map_err(ProtoError::from)?; - let nonce = crate::db::get_nonce(id).map_err(ProtoError::from)?; - verify_signature(nonce, sig).map_err(ProtoError::from)?; - - Ok(format!("config={config} nonce={nonce} sig={sig}")) -} - -// Simulates loading a config value. -// id=97 triggers InternalError2 ("config read failed"). -fn load_config(id: u32) -> Result> { - if id == 97 { - return Err(OneOf::new(InternalError2("config read failed".to_owned()))); - } - Ok(format!("cfg-{id}")) -} - -pub fn verify_signature(_nonce: u32, sig: &str) -> Result<(), OneOf<(InvalidSignature,)>> { - if sig != "ok" { - return Err(OneOf::new(InvalidSignature)); - } - Ok(()) -} - -type AuthError = OneOf<( - NotRegistered, - InvalidSignature, - InternalError1, - InternalError2, -)>; - -pub fn authenticate(id: u32, sig: &str) -> Result { - if id == 0 { - return Err(OneOf::new(NotRegistered)); - } - - // Return type AuthError lets the compiler infer the broaden target. - let nonce = crate::db::get_nonce(id).map_err(OneOf::broaden)?; - verify_signature(nonce, sig).map_err(OneOf::broaden)?; - - Ok(nonce) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn verify_signature_ok() { - assert!(verify_signature(42, "ok").is_ok()); - } - - #[test] - fn verify_signature_bad() { - let err = verify_signature(42, "bad").unwrap_err(); - assert!(err.narrow::().is_ok()); - } - - #[test] - fn authenticate_success() { - assert_eq!(authenticate(1, "ok").unwrap(), 42); - } - - #[test] - fn authenticate_not_registered() { - let err = authenticate(0, "ok").unwrap_err(); - assert!(err.narrow::().is_ok()); - } - - #[test] - fn authenticate_invalid_signature() { - let err = authenticate(1, "bad").unwrap_err(); - assert!(err.narrow::().is_ok()); - } - - #[test] - fn authenticate_internal_error1() { - let err = authenticate(99, "ok").unwrap_err(); - assert!(err.narrow::().is_ok()); - } - - #[test] - fn authenticate_internal_error2() { - let err = authenticate(98, "ok").unwrap_err(); - assert!(err.narrow::().is_ok()); - } - - #[test] - fn process_request_success() { - let result = process_request(1, "ok").unwrap(); - assert!(result.contains("nonce=42")); - } - - #[test] - fn process_request_not_registered() { - let err = process_request(0, "ok").unwrap_err(); - assert!(matches!(err, crate::errors::ProtoError::NotRegistered)); - } - - #[test] - fn process_request_invalid_signature() { - let err = process_request(1, "bad").unwrap_err(); - assert!(matches!(err, crate::errors::ProtoError::InvalidSignature)); - } - - #[test] - fn process_request_internal_from_config() { - // id=97 → load_config returns InternalError2 - let err = process_request(97, "ok").unwrap_err(); - assert!( - matches!(err, crate::errors::ProtoError::Internal(ref msg) if msg == "config read failed") - ); - } - - #[test] - fn process_request_internal_from_db() { - // id=99 → get_nonce returns InternalError1 - let err = process_request(99, "ok").unwrap_err(); - assert!( - matches!(err, crate::errors::ProtoError::Internal(ref msg) if msg == "db pool unavailable") - ); - } -} diff --git a/server/crates/arbiter-terrors-poc/src/db.rs b/server/crates/arbiter-terrors-poc/src/db.rs deleted file mode 100644 index c2c4c5a..0000000 --- a/server/crates/arbiter-terrors-poc/src/db.rs +++ /dev/null @@ -1,38 +0,0 @@ -use crate::errors::{InternalError1, InternalError2}; -use terrors::OneOf; - -// Simulates fetching a nonce from a database. -// id=99 → InternalError1 (pool unavailable) -// id=98 → InternalError2 (query timeout) -pub fn get_nonce(id: u32) -> Result> { - match id { - 99 => Err(OneOf::new(InternalError1("db pool unavailable".to_owned()))), - 98 => Err(OneOf::new(InternalError2("query timeout".to_owned()))), - _ => Ok(42), - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn get_nonce_returns_nonce_for_valid_id() { - assert_eq!(get_nonce(1).unwrap(), 42); - } - - #[test] - fn get_nonce_returns_internal_error1_for_sentinel() { - let err = get_nonce(99).unwrap_err(); - let internal = err.narrow::().unwrap(); - assert_eq!(internal.0, "db pool unavailable"); - } - - #[test] - fn get_nonce_returns_internal_error2_for_sentinel() { - let err = get_nonce(98).unwrap_err(); - let e = err.narrow::().unwrap_err(); - let internal = e.take::(); - assert_eq!(internal.0, "query timeout"); - } -} diff --git a/server/crates/arbiter-terrors-poc/src/errors.rs b/server/crates/arbiter-terrors-poc/src/errors.rs deleted file mode 100644 index cb436e1..0000000 --- a/server/crates/arbiter-terrors-poc/src/errors.rs +++ /dev/null @@ -1,130 +0,0 @@ -use terrors::OneOf; - -// Wire boundary type — what would go into a proto response -#[derive(Debug)] -pub enum ProtoError { - NotRegistered, - InvalidSignature, - Internal(String), // Or Box, who cares? -} - -// Internal terrors types -#[derive(Debug)] -pub struct NotRegistered; -#[derive(Debug)] -pub struct InvalidSignature; -#[derive(Debug)] -pub struct InternalError1(pub String); -#[derive(Debug)] -pub struct InternalError2(pub String); - -// Errors can be scattered across the codebase as long as they implement Into -impl From for ProtoError { - fn from(_: NotRegistered) -> Self { - ProtoError::NotRegistered - } -} - -impl From for ProtoError { - fn from(_: InvalidSignature) -> Self { - ProtoError::InvalidSignature - } -} - -impl From for ProtoError { - fn from(e: InternalError1) -> Self { - ProtoError::Internal(e.0) - } -} -impl From for ProtoError { - fn from(e: InternalError2) -> Self { - ProtoError::Internal(e.0) - } -} - -/// Private helper trait for converting from OneOf where each T can be converted -/// into the target type `O` by recursively narrowing until a match is found. -/// -/// IDK why this isn't already in terrors. -trait DrainInto: terrors::TypeSet + Sized { - fn drain(e: OneOf) -> O; -} - -macro_rules! impl_drain_into { - ($head:ident) => { - impl<$head, O> DrainInto for ($head,) - where - $head: Into + 'static, - { - fn drain(e: OneOf<($head,)>) -> O { - e.take().into() - } - } - }; - ($head:ident, $($tail:ident),+) => { - impl<$head, $($tail),+, O> DrainInto for ($head, $($tail),+) - where - $head: Into + 'static, - ($($tail,)+): DrainInto, - { - fn drain(e: OneOf<($head, $($tail),+)>) -> O { - match e.narrow::<$head, _>() { - Ok(h) => h.into(), - Err(rest) => <($($tail,)+)>::drain(rest), - } - } - } - impl_drain_into!($($tail),+); - }; -} - -// Generates impls for all tuple sizes from 1 up to 7 (restricted by terrors internal impl). -// Each invocation produces one impl then recurses on the tail. -impl_drain_into!(A, B, C, D, E, F, G, H, I); - -// Blanket From impl: body delegates to the recursive drain. -impl> From> for ProtoError { - fn from(e: OneOf) -> Self { - E::drain(e) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn not_registered_converts_to_proto() { - let e: ProtoError = NotRegistered.into(); - assert!(matches!(e, ProtoError::NotRegistered)); - } - - #[test] - fn invalid_signature_converts_to_proto() { - let e: ProtoError = InvalidSignature.into(); - assert!(matches!(e, ProtoError::InvalidSignature)); - } - - #[test] - fn internal_converts_to_proto() { - let e: ProtoError = InternalError1("boom".into()).into(); - assert!(matches!(e, ProtoError::Internal(msg) if msg == "boom")); - } - - #[test] - fn one_of_remainder_converts_to_proto_invalid_signature() { - use terrors::OneOf; - let e: OneOf<(InvalidSignature, InternalError1)> = OneOf::new(InvalidSignature); - let proto = ProtoError::from(e); - assert!(matches!(proto, ProtoError::InvalidSignature)); - } - - #[test] - fn one_of_remainder_converts_to_proto_internal() { - use terrors::OneOf; - let e: OneOf<(InvalidSignature, InternalError1)> = - OneOf::new(InternalError1("db fail".into())); - let proto = ProtoError::from(e); - assert!(matches!(proto, ProtoError::Internal(msg) if msg == "db fail")); - } -} diff --git a/server/crates/arbiter-terrors-poc/src/main.rs b/server/crates/arbiter-terrors-poc/src/main.rs deleted file mode 100644 index f17e180..0000000 --- a/server/crates/arbiter-terrors-poc/src/main.rs +++ /dev/null @@ -1,43 +0,0 @@ -mod auth; -mod db; -mod errors; - -use errors::ProtoError; - -fn run(id: u32, sig: &str) { - print!("authenticate(id={id}, sig={sig:?}) => "); - match auth::authenticate(id, sig) { - Ok(nonce) => println!("Ok(nonce={nonce})"), - Err(e) => match e.narrow::() { - Ok(_) => println!("Err(NotRegistered) — handled locally"), - Err(remaining) => { - let proto = ProtoError::from(remaining); - println!("Err(ProtoError::{proto:?}) — forwarded to wire"); - } - }, - } -} - -fn run_process(id: u32, sig: &str) { - print!("process_request(id={id}, sig={sig:?}) => "); - match auth::process_request(id, sig) { - Ok(s) => println!("Ok({s})"), - Err(e) => println!("Err(ProtoError::{e:?})"), - } -} - -fn main() { - println!("=== authenticate ==="); - run(0, "ok"); // NotRegistered - run(1, "bad"); // InvalidSignature - run(99, "ok"); // InternalError1 - run(98, "ok"); // InternalError2 - run(1, "ok"); // success - - println!("\n=== process_request (Try chain) ==="); - run_process(0, "ok"); // NotRegistered (guard, no I/O) - run_process(97, "ok"); // InternalError2 from load_config - run_process(99, "ok"); // InternalError1 from get_nonce - run_process(1, "bad"); // InvalidSignature from verify_signature - run_process(1, "ok"); // success -}