chore: remove invalidly committed PoC crate
This commit is contained in:
@@ -1,7 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "arbiter-terrors-poc"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2024"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
terrors = "0.3"
|
|
||||||
@@ -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<ProtoError>, 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<String, ProtoError> {
|
|
||||||
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<String, OneOf<(InternalError2,)>> {
|
|
||||||
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<u32, AuthError> {
|
|
||||||
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::<crate::errors::InvalidSignature, _>().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::<crate::errors::NotRegistered, _>().is_ok());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn authenticate_invalid_signature() {
|
|
||||||
let err = authenticate(1, "bad").unwrap_err();
|
|
||||||
assert!(err.narrow::<crate::errors::InvalidSignature, _>().is_ok());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn authenticate_internal_error1() {
|
|
||||||
let err = authenticate(99, "ok").unwrap_err();
|
|
||||||
assert!(err.narrow::<crate::errors::InternalError1, _>().is_ok());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn authenticate_internal_error2() {
|
|
||||||
let err = authenticate(98, "ok").unwrap_err();
|
|
||||||
assert!(err.narrow::<crate::errors::InternalError2, _>().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")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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<u32, OneOf<(InternalError1, InternalError2)>> {
|
|
||||||
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::<crate::errors::InternalError1, _>().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::<crate::errors::InternalError1, _>().unwrap_err();
|
|
||||||
let internal = e.take::<crate::errors::InternalError2>();
|
|
||||||
assert_eq!(internal.0, "query timeout");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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<dyn Error>, 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<ProtoError>
|
|
||||||
impl From<NotRegistered> for ProtoError {
|
|
||||||
fn from(_: NotRegistered) -> Self {
|
|
||||||
ProtoError::NotRegistered
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<InvalidSignature> for ProtoError {
|
|
||||||
fn from(_: InvalidSignature) -> Self {
|
|
||||||
ProtoError::InvalidSignature
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<InternalError1> for ProtoError {
|
|
||||||
fn from(e: InternalError1) -> Self {
|
|
||||||
ProtoError::Internal(e.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<InternalError2> for ProtoError {
|
|
||||||
fn from(e: InternalError2) -> Self {
|
|
||||||
ProtoError::Internal(e.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Private helper trait for converting from OneOf<T...> 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<O>: terrors::TypeSet + Sized {
|
|
||||||
fn drain(e: OneOf<Self>) -> O;
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! impl_drain_into {
|
|
||||||
($head:ident) => {
|
|
||||||
impl<$head, O> DrainInto<O> for ($head,)
|
|
||||||
where
|
|
||||||
$head: Into<O> + 'static,
|
|
||||||
{
|
|
||||||
fn drain(e: OneOf<($head,)>) -> O {
|
|
||||||
e.take().into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
($head:ident, $($tail:ident),+) => {
|
|
||||||
impl<$head, $($tail),+, O> DrainInto<O> for ($head, $($tail),+)
|
|
||||||
where
|
|
||||||
$head: Into<O> + 'static,
|
|
||||||
($($tail,)+): DrainInto<O>,
|
|
||||||
{
|
|
||||||
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<E: DrainInto<ProtoError>> From<OneOf<E>> for ProtoError {
|
|
||||||
fn from(e: OneOf<E>) -> 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"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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::<errors::NotRegistered, _>() {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user