I would like to call a decorator function before any call to methods on a struct, and modify the input arguments to that function. I've used decorators in dynamically typed languages like python and javascript before, but I can't wrap my head around how to do this in rust.
This is my usecase
I'm implementing a repository struct for an API im writing. The repository struct has methods to persist some data in an SQL database, and for that it creates a pooled connection on it's creation. That pooled connection is available at self.pool in the methods.
In order to execute an SQL query I need to get a connection from the pool by calling self.pool.get() which returns a Result<connection, Error>.
I would like to create a decorator that runs before any method-call on this struct, calls self.pool.get(), handles any errors and gives the resulting connection to the method that's beeing called.
This is what my code (without decorators) look like right now - calling self.pool.get() in each method will create alot of code duplication
impl IDriven<OwnerEntity> for Repository {
type Representation = Owner;
type Error = DomainError;
fn create(&self, new_owner: OwnerEntity) -> Result<Owner, Self::Error> {
let c = self.pool.get().expect("Could not get connection");
let owner: Result<Owner, Error> = diesel::insert_into(schema::owner::table)
.values(OwnerNew::from(new_owner))
.get_result(&c);
match owner {
Ok(owner) => Ok(owner),
Err(e) => Err(DomainError::Unknown),
}
}
}
If you want to be faithful to the decorator pattern, you could create an attribute macro that you apply to the impl block. The macro could re-emit the body of each function with the initial pool.get() etc. part added.
Otherwise, I think a simple closure/callback-based API could work for refactoring that particular part.
thanks for the clarification and example I mostly get it now.
So if I would like to handle errors better, could I perhaps do that directly in with_conn, i.e returning a DomainError::Conflict for diesel conflicts, and DomainError::NotFound for not found etc.?
I'm not so familiar with map_err - can it be used to map many diffrent error-types at once, or will it just always capture any error and translate it to one other error (in this case DomainError::Unknown)?
What I'm trying to say is I want a common way to handle SQL-errors, and I was asking if map_err() could be used to do that.
If i understand the signature correct I guess i could do something like this?
impl Repository {
fn with_conn<T, F>(&self, callback: F) -> Result<T, DomainError>
where
F: FnOnce(Connection) -> T
{
let c = self.pool.get().expect("Could not get connection");
callback(c).map_err(|SomeSQLError| match SomeSQLError {
SQLError::Conflict => DomainError::Conflict,
SQLError::NotFound = DomainError::NotFound,
_ => DomainError::Unknown
} )
}