I have this working code.
use thiserror::Error;
#[derive(Error, Debug)]
pub enum Error {
#[error("MyError: {0}")]
MyError(String),
#[error("Conversion error: {0}")]
ConversionError(#[from] std::io::Error),
}
// Common types
pub type Result<T> = std::result::Result<T, Error>;
struct StoredEvent {
id: String,
}
trait ApplicationEvent: TryFrom<StoredEvent> {}
trait Decider {
type Event: ApplicationEvent;
fn decide(event: Self::Event) -> Result<()>;
}
// Domain specific
struct BusinessEvent {
id: String,
}
impl ApplicationEvent for BusinessEvent {}
impl TryFrom<StoredEvent> for BusinessEvent {
type Error = std::io::Error;
fn try_from(stored_event: StoredEvent) -> std::result::Result<Self, Self::Error> {
Ok(Self {
id: stored_event.id.clone(),
})
}
}
struct BusinessDecider;
impl Decider for BusinessDecider {
type Event = BusinessEvent;
fn decide(_event: Self::Event) -> Result<()> {
Ok(())
}
}
struct Processor<D: Decider> {
decider: D,
}
impl<D: Decider> Processor<D>
where
Error: From<<<D as Decider>::Event as TryFrom<StoredEvent>>::Error>,
{
fn with(decider: D) -> Self {
Self { decider }
}
fn process_all(&self) -> Result<Vec<D::Event>> {
let stored_events = vec![
StoredEvent { id: "1".to_owned() },
StoredEvent { id: "2".to_owned() },
StoredEvent { id: "3".to_owned() },
StoredEvent { id: "4".to_owned() },
];
let mut result = vec![];
for obj in stored_events {
let converted: D::Event = obj.try_into()?;
result.push(converted);
}
Ok(result)
}
}
#[cfg(test)]
mod tests {
use crate::*;
#[test]
fn multiple_works() {
let decider = BusinessDecider {};
let processor = Processor::with(decider);
let event = processor.process_all().unwrap();
assert_eq!("1", event[0].id);
assert_eq!("2", event[1].id);
assert_eq!("3", event[2].id);
assert_eq!("4", event[3].id);
}
}
The interesting bits are the where
clause
where
Error: From<<<D as Decider>::Event as TryFrom<StoredEvent>>::Error>,
and the for
loop
let mut result = vec![];
for obj in stored_events {
let converted: D::Event = obj.try_into()?;
result.push(converted);
}
Without the where
clause and the From<io::Error>
generated by the thiserror
error conversion, I get this compiler error:
error[E0271]: type mismatch resolving `<<D as Decider>::Event as TryFrom<StoredEvent>>::Error == Error`
--> src/lib.rs:72:43
|
72 | let converted: D::Event = obj.try_into()?;
| ^^^^^^^^ expected associated type, found enum `Error`
|
= note: expected associated type `<<D as Decider>::Event as TryFrom<StoredEvent>>::Error`
found enum `Error`
= help: consider constraining the associated type `<<D as Decider>::Event as TryFrom<StoredEvent>>::Error` to `Error` or calling a method that returns `<<D as Decider>::Event as TryFrom<StoredEvent>>::Error`
= note: for more information, visit https://doc.rust-lang.org/book/ch19-03-advanced-traits.html
The compiler thankfully came up with the where
clause after attempting to fix the compiler error by implementing the error conversion.
I then wanted to improve on the for
loop with into_iter
on the collection like so:
let result = stored_events
.into_iter()
.map(|event| event.try_into())
.collect::<Result<Vec<D::Event>>>()?;
but I still get the above error even with the left-in error conversion and where
clause:
error[E0271]: type mismatch resolving `<<D as Decider>::Event as TryFrom<StoredEvent>>::Error == Error`
--> src/lib.rs:79:32
|
79 | .map(|event| event.try_into())
| ^^^^^^^^ expected associated type, found enum `Error`
|
= note: expected associated type `<<D as Decider>::Event as TryFrom<StoredEvent>>::Error`
found enum `Error`
= help: consider constraining the associated type `<<D as Decider>::Event as TryFrom<StoredEvent>>::Error` to `Error`
= note: for more information, visit https://doc.rust-lang.org/book/ch19-03-advanced-traits.html
My questions are two-fold:
- Why does the current solution work for the
for
loop but notinto_iter
? - Is there a better way to specify the
Error
-TryFrom<StoredEvent>>::Error
constraint "earlier" in the trait/type hierarchy?