use super::fields::*;
use crate::{File, Record};
use encoding_rs::mem::{encode_latin1_lossy, is_str_latin1};
use std::io::{Cursor, Write};
use thiserror::Error;
#[derive(Error, Debug)]
pub enum EncodeError {
#[error(transparent)]
Io(#[from] std::io::Error),
#[error("invalid encoding: {0}")]
InvalidEncoding(String),
}
pub fn encode_file(file: &File) -> Result<Vec<u8>, EncodeError> {
let mut writer = Writer::new(Cursor::new(Vec::new()));
writer.write(file)?;
let buffer = writer.into_inner().into_inner();
Ok(buffer)
}
#[derive(Clone)]
pub struct Writer<W: Write> {
writer: W,
}
impl<W: Write> Writer<W> {
pub fn new(inner: W) -> Self {
Self { writer: inner }
}
pub fn write(&mut self, file: &File) -> Result<(), EncodeError> {
self.write_version(file.version)?;
for record in &file.records {
self.write_record(record)?;
}
Ok(())
}
fn write_version(&mut self, version: u32) -> Result<(), EncodeError> {
self.writer.write_fmt(format_args!("{:06x?}", version))?;
self.writer.write_all(b"\n")?;
Ok(())
}
fn write_record(&mut self, record: &Record) -> Result<(), EncodeError> {
self.write_str(&record.flarm_id, FLARM_ID_LENGTH)?;
self.write_str(&record.pilot_name, PILOT_NAME_LENGTH)?;
self.write_str(&record.airfield, AIRFIELD_LENGTH)?;
self.write_str(&record.plane_type, PLANE_TYPE_LENGTH)?;
self.write_str(&record.registration, REGISTRATION_LENGTH)?;
self.write_str(&record.call_sign, CALL_SIGN_LENGTH)?;
self.write_str(&record.frequency, FREQUENCY_LENGTH)?;
self.writer.write_all(b"\n")?;
Ok(())
}
pub(crate) fn write_str(&mut self, value: &str, length: usize) -> Result<(), EncodeError> {
if !is_str_latin1(value) {
return Err(EncodeError::InvalidEncoding(value.to_string()));
}
let bytes = encode_latin1_lossy(value);
for byte in bytes.iter().take(length) {
self.writer.write_fmt(format_args!("{:02x?}", byte))?;
}
let bytes_len = bytes.len();
if bytes_len < length {
let placeholders = b"20".repeat(length - bytes_len);
self.writer.write_all(&placeholders)?;
}
Ok(())
}
pub fn into_inner(self) -> W {
self.writer
}
}
#[cfg(test)]
mod tests {
use super::{EncodeError, Writer};
use insta::assert_debug_snapshot;
use std::io::Cursor;
fn encode_str(value: &str, length: usize) -> Result<String, EncodeError> {
let mut writer = Writer::new(Cursor::new(Vec::new()));
writer.write_str(value, length)?;
let bytes = writer.into_inner().into_inner();
Ok(String::from_utf8(bytes).unwrap())
}
#[test]
fn encoding_works() {
assert_eq!(encode_str("D-4711", 7).unwrap(), "442d3437313120");
assert_eq!(encode_str("1234567890", 7).unwrap(), "31323334353637");
assert_eq!(encode_str("Müller", 10).unwrap(), "4dfc6c6c657220202020");
}
#[test]
fn encoding_fails_for_non_latin1() {
assert_debug_snapshot!(
encode_str("😅", 7).unwrap_err(),
@r###"
InvalidEncoding(
"😅",
)
"###);
}
}