Skip to main content

copybook_codec/file/
dispatch.rs

1// SPDX-License-Identifier: AGPL-3.0-or-later
2//! Operation-level fixed/RDW dispatch for codec callers.
3//!
4//! The framing crates own byte-level parsing and writing. This module owns the
5//! format choice used by codec operations and keeps the legacy single-record
6//! helpers available through [`crate::record`].
7
8use crate::options::RecordFormat;
9use copybook_error::{Error, ErrorCode, Result};
10use std::io::{Read, Write};
11
12pub use copybook_fixed::{FixedRecordReader, FixedRecordWriter};
13pub use copybook_rdw::{
14    BDW_HEADER_LEN, BDW_MAX_BLOCK_LEN, BdwHeader, RDW_HEADER_LEN, RDWRecord, RDWRecordReader,
15    RDWRecordWriter, VB_MAX_RECORD_LEN, VbBlockReader, VbBlockWriter, VbRecord,
16};
17
18/// Read one record using the selected framing format.
19///
20/// # Errors
21/// Returns an error when the delegated framing read fails or when fixed
22/// framing is missing its LRECL.
23#[inline]
24#[must_use = "Handle the Result or propagate the error"]
25pub fn read_record(
26    input: &mut impl Read,
27    format: RecordFormat,
28    lrecl: Option<u32>,
29) -> Result<Option<Vec<u8>>> {
30    match format {
31        RecordFormat::Fixed => read_fixed_record(input, lrecl),
32        RecordFormat::RDW => {
33            read_rdw_record(input, false).map(|record| record.map(|record| record.payload))
34        }
35        RecordFormat::Vb => {
36            // Single-shot read: callers needing block iteration across calls
37            // use `VbBlockReader` (or the record iterator) directly.
38            let mut reader = VbBlockReader::new(input, false);
39            reader
40                .read_record()
41                .map(|record| record.map(|record| record.payload))
42        }
43    }
44}
45
46#[inline]
47fn read_fixed_record(input: &mut impl Read, lrecl: Option<u32>) -> Result<Option<Vec<u8>>> {
48    let mut reader = FixedRecordReader::new(input, lrecl)?;
49    reader.read_record()
50}
51
52/// Read one complete RDW record, preserving its header and reserved bytes.
53///
54/// Use [`read_record`] when the operation only needs the payload. This helper
55/// is the lossless dispatch path for callers that need to inspect or preserve
56/// the original RDW framing metadata.
57///
58/// # Errors
59/// Returns an error when the delegated RDW framing read fails.
60#[inline]
61#[must_use = "Handle the Result or propagate the error"]
62pub fn read_rdw_record(input: &mut impl Read, strict_mode: bool) -> Result<Option<RDWRecord>> {
63    let mut reader = RDWRecordReader::new(input, strict_mode);
64    reader.read_record()
65}
66
67/// Write one record using the selected framing format.
68///
69/// # Errors
70/// Returns an error when the delegated framing write fails.
71#[inline]
72#[must_use = "Handle the Result or propagate the error"]
73pub fn write_record(output: &mut impl Write, data: &[u8], format: RecordFormat) -> Result<()> {
74    match format {
75        RecordFormat::Fixed => {
76            output.write_all(data).map_err(|e| {
77                Error::new(
78                    ErrorCode::CBKR101_FIXED_RECORD_ERROR,
79                    format!("Write error: {e}"),
80                )
81            })?;
82            Ok(())
83        }
84        RecordFormat::RDW => {
85            let mut writer = RDWRecordWriter::new(output);
86            writer.write_record_from_payload(data, None)
87        }
88        RecordFormat::Vb => {
89            // Single-shot write of one block; callers packing multiple
90            // records use `VbBlockWriter` (or the encode path) directly.
91            let mut writer = VbBlockWriter::new(output);
92            writer.write_record_from_payload(data, 0)?;
93            writer.finish()
94        }
95    }
96}