yule_log_macros 0.3.3

Derive macros for the yule_log ULOG parser crate.
Documentation
#[derive(ULogData)]
struct UserStruct {}
       ↓
Implements ULogAccess for the struct `UserStruct`
       ↓
Requires generating:
   ├── Accessor struct (Self::Accessor)
   │     - Named `UserStructAccessor`.
   │     - Contains one `usize` field per struct field in `UserStruct`.
   │     - Each field holds the **index** of the corresponding ULog field.
   │       (based on its name, or overridden via `#[yule_log(field_name = "...")]`).
   │
   ├── Accessor::from_format(&def::Format) -> Result<Accessor, ULogError>
   │     - Takes a ULog schema definition (`def::Format`)
   │     - Builds a map of field name → index from that schema.
   │     - Constructs the accessor struct by looking up each expected field.
   │     - Returns error if any field is missing.
   │
   ├── Accessor::get_data(&inst::Format) -> Result<UserStruct, ULogError>
   │     - Takes a **message instance** (same shape as the schema).
   │     - Uses the precomputed indexes to grab each field.
   │     - Calls `<FieldType as FromField>::from_field(...)` to extract each value.
   │     - Constructs and returns the actual `UserStruct` by building a struct literal.
   │
   ├── impl FromField for UserStruct
   │     - Enables the struct to be parsed as a nested field withing a ULog message.
   │     - Match pattern: `FieldValue::ScalarOther(inst_format)`.
   │     - Delegates to the accessor: builds it using the format, then calls `get_data`.
   │     - Fails with a `TypeMismatch` error if the field isn't a nested struct.
   │
   └── impl FromField for Vec<UserStruct>
         - Enables deserialization from an array of nested structs within a ULOGMessage.
         - Match pattern: `FieldValue::ArrayOther(Vec<InstFormat>)`.
         - Builds one accessor (from first instance's format).
         - Iterates over each element and uses the accessor to get data.
         - Returns a `Vec<UserStruct>`, or an error if types mismatch.


#[derive(ULogMessages)]
enum LoggedMessages {
    VehicleLocalPosition(VehicleLocalPosition),
    Other(yule_log::model::msg::UlogMessage),
}
       ↓
Generates an `impl LoggedMessages` with a `.stream()` method:
   └── LoggedMessages::stream(reader: impl Read) -> Result<impl Iterator<Item = Result<LoggedMessages, ULogError>>>

       ↓
Requires generating:

   ├── Internal accessor enum: __yule_log_derive_LoggedMessagesAccessor
   │     - Enum with one variant per message type (excluding `forward_other`)
   │     - Each variant wraps the corresponding struct's accessor (e.g. VehicleLocalPositionAccessor)
   │     - Used at runtime to call `.get_data()` for each message
   │
   ├── Internal iterator struct: __yule_log_derive_LoggedMessages<R>
   │     - Holds the actual `ULogParser<R>` and a map: msg_id → accessor enum
   │     - Implements Iterator<Item = Result<LoggedMessages, ULogError>>
   │
   ├── impl Iterator for __yule_log_derive_LoggedMessages<R>
   │     - Drives the message stream
   │     - On `AddSubscription`:
   │         - Matches the subscription’s `message_name` and `multi_id` against known enum variants
   │         - If a match is found:
   │             - Retrieves the schema format.
   │             - Constructs an Accessor by calling <VariantType as ULogAccess>::from_format(def::Format).
   │             - Stores Accessor instance in msg_id → accessor map.
   │     - On `LoggedData`:
   │         - Looks up the Accessor by `msg_id`.
   │         - If found, uses the stored accessor to extract a value from the message.
   │         - Wraps it in the corresponding enum variant and yields it.
   │     - On unmatched messages:
   │         - If a variant is marked #[yule_log(forward_other)]:
   │             - Wraps the full UlogMessage in that variant and yields it.
   │
   ├── Compile-time checks
         - Only tuple-style enum variants supported (VariantName(Type)).
         - No generics or where clauses allowed.
         - Exactly one #[yule_log(forward_other)] allowed.

   - Runtime message dispatch logic:
         - Obtains `(message_name, multi_id)` pairs from the constants generated by the `ULogData` derive on each variant struct.
           (e.g., `__YULE_LOG_SUBSCRIPTION`, `__YULE_LOG_MULTI_ID`)
         - Matches these pairs against `AddSubscription` messages emitted by the parser at runtime.
         - Generates a match arm to:
             - Calls `<VariantType as ULogAccess>::from_format(def::Format)` to construct the accessor
             - Stores the accessor keyed by `msg_id` for efficient lookup.
         - On receiving `LoggedData` messages with a `msg_id`:
             - Looks up the stored accessor by `msg_id`.
             - Calls `Accessor.get_data()` to deserialize the payload into `UserStruct`.
             - Wraps decoded data in the corresponding `LoggedMessages` enum variant.
         - For messages with no matching subscription:
             - If the enum contains a variant is marked `#[yule_log(forward_other)]`, wraps and yields the raw message in that variant.


Handoff between ULogData and ULogMessages derives:
  - `ULogData` derives implement `ULogAccess` on individual structs.
  - `ULogAccess` defines how to build accessors and decode data for that struct.
  - `ULogMessages` derives expect enum variants to wrap structs implementing `ULogAccess`.
  - At runtime, `ULogMessages`:
    - Uses constants from `ULogData` (like `__YULE_LOG_SUBSCRIPTION`) to match subscription messages.
    - Calls `<VariantType as ULogAccess>::from_format` to build accessors for variants.
    - Stores accessors keyed by `msg_id`.
    - Uses accessor's `get_data()` to decode logged data messages.
    - Wraps decoded data in the enum variant for user consumption.