Skip to content

Conversation

@michaeldjeffrey
Copy link
Contributor

Move Domain-Specific Errors from file-store to Implementers

Summary

This PR restructures error handling in the file-store library by moving domain-specific errors to the types implementing TryFrom conversions. This separation of concerns keeps file-store focused on library-level errors while allowing each domain to handle its own conversion errors appropriately.

Changes

file_store::Error Cleanup

The file_store::Error enum has been significantly reduced to contain only errors that can be returned from file-store library code:

  • IO, encoding, channel, AWS, crypto, and database errors remain
  • All domain-specific errors (IoT, mobile, subscriber) have been removed
  • Domain errors now live in the Error type of their respective TryFrom implementations

Custom Domain Error Types

Created domain-specific error types in file-store-oracles.
Each file has it's own Error type that maps things that can fail during those types conversions.

Some common errors include

#[error("invalid timestamp: {0}")]
Timestamp(#[from] TimestampDecodeError),

#[error("missing field: {0}")]
MissingField(&'static str),

MsgDecode Trait Enhancement

The MsgDecode trait now includes a custom error variant:

pub enum MsgDecodeError<E> {
    Prost(prost::DecodeError),
    Conversion(E),
}

This allows implementers to return domain-specific errors from the TryFrom conversion without coupling to file-store's error types.

MsgTimestamp Trait Relocation

The MsgTimestamp trait has been removed from file-store due to Rust's orphan rules. When implementing this trait for both proto and domain types, you need to own the trait definition. The trait definition and usage examples are documented in the timestamp.rs file with guidance for implementers.

Timestamp Handling Improvements

  • TimestampDecode trait and TimestampDecodeError remain in file-store for shared timestamp logic
  • TimestampEncode trait added for encoding DateTime to various timestamp formats
  • Domain implementations can now use timestamp decoding without coupling their error types to file-store

Enum Conversion Utility

Added prost_enum helper function in file-store-oracles:

pub fn prost_enum<Enum, Op, Err>(value: i32, map_err: Op) -> Result<Enum, Err>
where
    Enum: TryFrom<i32, Error = prost::UnknownEnumValue>,
    Op: FnOnce(prost::UnknownEnumValue) -> Err,

This provides a consistent way to convert prost i32 values to enums while properly propagating errors. The mapper automatically determines the enum type from context and allows custom error handling.

FileInfoPollerParser Changes

Updated to allow user errors to pass through from TryFrom implementations, enabling better error context propagation from domain-specific conversions.

IoT Beacon Conversion

The conversion from IotBeaconReport to a regular beacon no longer returns a Result as it's a pure transformation.

Code Organization

  • Struct fields in domain types have been reordered where necessary to satisfy Rust's lifetime rules during conversions
  • File-store dependency in file-store-oracles reduced with removal of domain error types

@michaeldjeffrey michaeldjeffrey force-pushed the mj/file-store-err-flip branch 2 times, most recently from 7020c14 to e859bbf Compare October 29, 2025 20:40
`TimestampDecodeError` provides more explicit errors for what type of 
field we are attempting to decode, and an extra if we cannot parse the 
incoming number.

`TimestampDecodeResult` is provided as a convenience for implementing 
MsgTimestamp.

```
impl MsgTimeStamp<Result<DateTime<Utc>, TimestampDecodeError>> for Type {}
```
is super cumbersome to read.
This will cuase the compiler to err if you try to implement MsgDecoe for
a type without also having the TryFrom impl.

Before, that error would only be thrown if someone tried to call 
::decode() on a msg.

We also provide a custom decode error, this allows us to tell the 
different between prost failing to decode a protobuf vs. our own 
conversion code failing to transform into a domain struct.

This also allows us to provide our own error for conversions instead of 
being tied to `file_store::Error` thta had become a grab bag of domain 
specific err variants.
Remove the constraint on MsgDecode implementers returning a 
`file_store::Error` in lieu of their own domain error. The parser will 
pass them back their own error if something fails, and they can be more 
explicit about the errors they care about. 

Rather than trying to decide between putting a new err variant in 
file-store, or using ExternalError, or putting a specific string in the 
wrong err type for what they actual are trying to communicate.
Use the member field type to figure out what enum we’re casting into. 
Provide an err variant that wraps a `prost::UnknownEnumValue` for 
propogating unsupported variants in TryFrom impls.
… from file-store lib code

All domain specific errors are going to move into the err variant of
TryFrom for the type that is being converted to.
Includes the normal `TimestampDecodeError` provided by file-store. 
MissingField when trying to access nested types to convert. 

And wrapping `prost::UnknownEnumValue` errors to better know which enum 
we could not map, and have a consistent way of treating enum 
conversions.

Before we had a smattering of using `Enum::try_from(proto.enum_field)` 
which allowed is to catch errs. And `proto.enum_field()` using the 
provided getter that will panic if the value is unknown. 

Now we have a `prost_enum(proto.enum_field, Error::EnumField)` mapper 
being provided that can figure out what type we’re trying to convert 
into and forward any errors back to us for bubbling.
You may notice an `h3o::error::InvalidCellIndex` error in usage counts.

One of the benefits for flipping error logic over to the implementer is 
now file-store has no more reason to know about h3o.
These 2 files are essentially the same and are good candidates for 
moving into networ_common in file-store-oracles. That is outside the 
scope of this PR.
Many of the streams in file-store are still heavily tied to 
`file_store::Error`. Passing reward manifest files still requires us 
mapping into a proper lib error so we can pass it to a lib function.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants