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::CBKF104_RDW_SUSPECT_ASCII,
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::CBKF104_RDW_SUSPECT_ASCII,
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]
117 #[must_use = "Handle the Result or propagate the error"]
118 pub fn write_record_from_payload(
119 &mut self,
120 payload: &[u8],
121 preserve_reserved: Option<u16>,
122 ) -> Result<()> {
123 let length = payload.len();
124 let header =
125 RdwHeader::from_payload_len(length, preserve_reserved.unwrap_or(0)).map_err(|_| {
126 Error::new(
127 ErrorCode::CBKE501_JSON_TYPE_MISMATCH,
128 format!(
129 "RDW payload too large: {length} bytes exceeds maximum of {}",
130 u16::MAX
131 ),
132 )
133 .with_context(ErrorContext {
134 record_index: Some(self.record_count + 1),
135 field_path: None,
136 byte_offset: None,
137 line_number: None,
138 details: Some("RDW length field is 16-bit".to_string()),
139 })
140 })?;
141
142 let record = RDWRecord {
143 header: header.bytes(),
144 payload: payload.to_vec(),
145 };
146 self.write_record(&record)
147 }
148
149 #[inline]
154 #[must_use = "Handle the Result or propagate the error"]
155 pub fn flush(&mut self) -> Result<()> {
156 self.output.flush().map_err(|e| {
157 Error::new(
158 ErrorCode::CBKF104_RDW_SUSPECT_ASCII,
159 format!("I/O error flushing output: {e}"),
160 )
161 })
162 }
163
164 #[inline]
166 #[must_use]
167 pub fn record_count(&self) -> u64 {
168 self.record_count
169 }
170}