1use crate::{RDW_MAX_PAYLOAD_LEN, RDWRecord, RdwHeader};
2use copybook_error::{Error, ErrorCode, ErrorContext, Result};
3use std::io::Write;
4use tracing::debug;
5
6#[derive(Debug)]
8pub struct RDWRecordWriter<W: Write> {
9 output: W,
10 record_count: u64,
11}
12
13impl<W: Write> RDWRecordWriter<W> {
14 #[inline]
16 #[must_use]
17 pub fn new(output: W) -> Self {
18 Self {
19 output,
20 record_count: 0,
21 }
22 }
23
24 #[inline]
29 #[must_use = "Handle the Result or propagate the error"]
30 pub fn write_record(&mut self, record: &RDWRecord) -> Result<()> {
31 self.validate_record(record)?;
32
33 self.output.write_all(&record.header).map_err(|e| {
34 Error::new(
35 ErrorCode::CBKR202_RDW_WRITE_ERROR,
36 format!("I/O error writing RDW header: {e}"),
37 )
38 .with_context(ErrorContext {
39 record_index: Some(self.record_count + 1),
40 field_path: None,
41 byte_offset: None,
42 line_number: None,
43 details: None,
44 })
45 })?;
46
47 self.output.write_all(&record.payload).map_err(|e| {
48 Error::new(
49 ErrorCode::CBKR202_RDW_WRITE_ERROR,
50 format!("I/O error writing RDW payload: {e}"),
51 )
52 .with_context(ErrorContext {
53 record_index: Some(self.record_count + 1),
54 field_path: None,
55 byte_offset: Some(4),
56 line_number: None,
57 details: None,
58 })
59 })?;
60
61 self.record_count += 1;
62 debug!(
63 "Wrote RDW record {} with {} byte payload",
64 self.record_count,
65 record.payload.len()
66 );
67 Ok(())
68 }
69
70 #[inline]
71 fn validate_record(&self, record: &RDWRecord) -> Result<()> {
72 let header_len = usize::from(record.length());
73 let payload_len = record.payload.len();
74
75 if payload_len > RDW_MAX_PAYLOAD_LEN {
76 return Err(Error::new(
77 ErrorCode::CBKF102_RECORD_LENGTH_INVALID,
78 format!(
79 "RDW payload too large: {payload_len} bytes exceeds maximum of {RDW_MAX_PAYLOAD_LEN}"
80 ),
81 )
82 .with_context(ErrorContext {
83 record_index: Some(self.record_count + 1),
84 field_path: None,
85 byte_offset: None,
86 line_number: None,
87 details: Some("RDW length field is 16-bit".to_string()),
88 }));
89 }
90
91 if header_len != payload_len {
92 return Err(Error::new(
93 ErrorCode::CBKF102_RECORD_LENGTH_INVALID,
94 format!(
95 "RDW header length mismatch: header declares {header_len} bytes, payload has {payload_len} bytes"
96 ),
97 )
98 .with_context(ErrorContext {
99 record_index: Some(self.record_count + 1),
100 field_path: None,
101 byte_offset: Some(0),
102 line_number: None,
103 details: Some(
104 "RDW header length must match payload length before writing".to_string(),
105 ),
106 }));
107 }
108
109 Ok(())
110 }
111
112 #[inline]
118 #[must_use = "Handle the Result or propagate the error"]
119 pub fn write_record_from_payload(
120 &mut self,
121 payload: &[u8],
122 preserve_reserved: Option<u16>,
123 ) -> Result<()> {
124 let length = payload.len();
125 let header = RdwHeader::from_payload_len(length, preserve_reserved.unwrap_or(0)).map_err(
126 |error| {
127 error.with_context(ErrorContext {
128 record_index: Some(self.record_count + 1),
129 field_path: None,
130 byte_offset: None,
131 line_number: None,
132 details: Some("RDW length field is 16-bit".to_string()),
133 })
134 },
135 )?;
136
137 let record = RDWRecord {
138 header: header.bytes(),
139 payload: payload.to_vec(),
140 };
141 self.write_record(&record)
142 }
143
144 #[inline]
149 #[must_use = "Handle the Result or propagate the error"]
150 pub fn flush(&mut self) -> Result<()> {
151 self.output.flush().map_err(|e| {
152 Error::new(
153 ErrorCode::CBKR202_RDW_WRITE_ERROR,
154 format!("I/O error flushing output: {e}"),
155 )
156 })
157 }
158
159 #[inline]
161 #[must_use]
162 pub fn record_count(&self) -> u64 {
163 self.record_count
164 }
165}