I have an EDI spec that I'm trying to implement in rust, namely the CWR format or Common Works Registration format. The purpose of the spec isn't really important but basically it consists of a bunch of different 'record types' all of which share a common field, and most of which share another two common fields. Here's the gist of it:
- A
filehas a header record, followed by one or moregroups, followed by a trailer record - A
grouphas a header record, followed by one or moretransactions, followed by a trailer record - A
transactionconsists of a set of records that describe a transaction
All of the records, from the file level down to the transaction level have a record type, so I tried modelling this with a trait:
pub trait CwrRecord : AsCwrRecord + std::fmt::Debug {
fn get_record_type(&self) -> RecordType;
}
pub enum RecordType {
HDR,
TRL,
GRH,
GRT,
SPU
// Rest omitted for brevity
}
All of the records at the transaction level have record type, as well as transaction number and record sequence number, so I also modelled this with a trait:
pub trait CwrTransactionRecord: CwrRecord {
fn get_transaction_number(&self) -> u32;
fn get_record_sequence_number(&self) -> u32;
}
Because you can't directly cast between traits, even in a heirarchy, I had to also implement an associated generic function for all types that implement the CwrRecord trait, and did so with this trait and function:
pub trait AsCwrRecord {
fn as_cwr_record(&self) -> &dyn CwrRecord;
}
impl <T: CwrRecord> AsCwrRecord for T {
fn as_cwr_record(&self) -> &dyn CwrRecord {
self
}
}
All of these work great so far. So using these we can define a few record types, going to omit some of this for brevity:
Here is a file trailer record, for instance:
#[derive(Debug)]
pub struct TrlRecord {
pub group_count: u32,
pub transaction_count: u32,
pub record_count: u32,
}
impl CwrRecord for TrlRecord {
fn get_record_type(&self) -> RecordType {
RecordType::TRL
}
}
Here is one of the transactional record types:
#[derive(Debug)]
pub struct SpuRecord {
pub some_other_data: i32,
pub transaction_number: u32,
pub record_sequence_number: u32
}
impl CwrRecord for SpuRecord {
fn get_record_type(&self) -> RecordType { RecordType::SPU }
}
impl CwrTransactionRecord for SpuRecord {
fn get_transaction_number(&self) -> u32 {
self.transaction_number
}
fn get_record_sequence_number(&self) -> u32 {
self.record_sequence_number
}
}
All of this works great! But looking at the heirarchy from the beginning, we need to wrap these record types up into group and transaction (I am ignoring the file level for now):
#[derive(Debug)]
pub struct CwrTransaction<'a> {
records: Vec<&'a dyn CwrTransactionRecord>,
}
#[derive(Debug)]
pub struct CwrGroup<'a> {
pub group_header: GrhRecord,
pub transactions: Vec<CwrTransaction<'a>>,
pub group_trailer: GrtRecord,
}
Alright, so now we are at the part I am sort of stuck at - I want to be able to treat a transaction as something that can be iterated over to get records, no problem, I can implement IntoIterator for it:
impl<'a> IntoIterator for CwrTransaction<'a> {
type Item = &'a dyn CwrTransactionRecord;
type IntoIter = CwrTransactionIntoIter<'a>;
fn into_iter(self) -> Self::IntoIter {
CwrTransactionIntoIter {
transaction: self,
index: 0,
}
}
}
pub struct CwrTransactionIntoIter<'a> {
transaction: CwrTransaction<'a>,
index: usize,
}
impl<'a> Iterator for CwrTransactionIntoIter<'a> {
type Item = &'a dyn CwrTransactionRecord;
fn next(&mut self) -> Option<&'a dyn CwrTransactionRecord> {
let result = if self.index < self.transaction.records.len() {
self.index += 1;
Some(self.transaction.records[self.index - 1])
} else {
None
};
result
}
}
This works great as well! The real issue is starting to try to also treat CwrGroup as something that can be iterated over. I saw that there is a flatten() function that can be used, and I have successfully flattened a free-standing Vec<CwrTransaction> into just CwrTransactionRecords, however when I try to do it from a struct I run into issues:
impl<'a> IntoIterator for &'a CwrGroup<'a> {
type Item = &'a dyn CwrRecord;
type IntoIter = CwrGroupIntoIter<'a>;
fn into_iter(self) -> Self::IntoIter {
CwrGroupIntoIter {
has_returned_header: false,
has_returned_trailer: false,
group: &self,
transaction_iter: &mut self.transactions.into_iter().flatten()
}
}
}
pub struct CwrGroupIntoIter<'a> {
has_returned_header: bool,
has_returned_trailer: bool,
group: &'a CwrGroup<'a>,
transaction_iter: &'a mut dyn Iterator<Item = &'a dyn CwrTransactionRecord>
}
impl<'a> Iterator for CwrGroupIntoIter<'a> {
type Item = &'a dyn CwrRecord;
fn next(&mut self) -> Option<&'a dyn CwrRecord> {
if !self.has_returned_header {
self.has_returned_header = true;
return Some(&self.group.group_header);
}
if let Some(record) = self.transaction_iter.next() {
return Some(record.as_cwr_record());
}
todo!()
}
}
Some of the veterans can probably guess, I hit two issues here:
error[E0515]: cannot return value referencing temporary value
--> src/cwr/group.rs:16:10
|
16 | / CwrGroupIntoIter {
17 | | has_returned_header: false,
18 | | has_returned_trailer: false,
19 | | group: &self,
20 | | index: 0,
21 | | transaction_iter: &mut self.transactions.into_iter().flatten()
| | --------------------------------------- temporary value created here
22 | | }
| |_________^ returns a value referencing data owned by the current function
error[E0507]: cannot move out of `self.transactions` which is behind a shared reference
--> src/cwr/group.rs:21:36
|
21 | transaction_iter: &mut self.transactions.into_iter().flatten()
| ^^^^^^^^^^^^^^^^^ move occurs because `self.transactions` has type `Vec<transaction::CwrTransaction<'_>>`, which does not implement the `Copy` trait
error: aborting due to 2 previous errors; 1 warning emitted
How can I store an iterator for use in my CwrGroupIntoIter, and am I going about this the correct way (to have CwrGroup::into_iter give me back what I want - an iterator over &dyn CwrRecord)?