use crate::error::{Error, Result};
use crate::limits::Limits;
pub const MODEL_VERSION: u8 = 1;
pub const MAX_MODEL_RECORDS: u32 = 1 << 24;
pub const MAX_MODEL_FIELDS: u64 = 1 << 24;
pub const TERM_LF: u8 = 0;
pub const TERM_CRLF: u8 = 1;
pub const TERM_CR: u8 = 2;
pub const MIN_ROWS: usize = 3;
pub const MIN_COLS: usize = 2;
pub const MIN_GAP: usize = 2;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Column {
pub start: u64,
pub end: u64,
}
impl Column {
pub const fn width(&self) -> u64 {
self.end - self.start
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FwField {
pub start: u64,
pub end: u64,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FwRecord {
pub start: u64,
pub end: u64,
pub fields: Vec<FwField>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StreamRecord {
pub start: u64,
pub end: u64,
pub terminator: u8,
pub fields: Vec<FwField>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Layout {
pub bom_len: u8,
pub terminator: u8,
pub mixed_terminators: bool,
pub width: u64,
pub columns: Vec<Column>,
}
impl Layout {
pub fn column_count(&self) -> usize {
self.columns.len()
}
pub const fn terminator_name(&self) -> &'static str {
match self.terminator {
TERM_CRLF => "crlf",
TERM_CR => "cr",
_ => "lf",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FixedWidthModel {
pub doc_len: u64,
pub layout: Layout,
pub has_header: bool,
pub records: Vec<FwRecord>,
}
impl FixedWidthModel {
pub fn node(&self, index: u32) -> Option<&FwRecord> {
self.records.get(index as usize)
}
pub fn row_count(&self) -> usize {
self.records.len()
}
pub fn header(&self) -> Option<&FwRecord> {
self.records.first()
}
pub fn column_count(&self) -> usize {
self.layout.columns.len()
}
pub fn encode(&self) -> Vec<u8> {
let mut out = Vec::with_capacity(64 + self.records.len() * 24);
out.extend_from_slice(b"FWM1");
out.push(MODEL_VERSION);
out.push(self.layout.bom_len);
out.push(self.layout.terminator);
let mut flags = 0u8;
if self.has_header {
flags |= 1;
}
if self.layout.mixed_terminators {
flags |= 2;
}
out.push(flags);
out.extend_from_slice(&self.doc_len.to_le_bytes());
out.extend_from_slice(&self.layout.width.to_le_bytes());
out.extend_from_slice(&(self.layout.columns.len() as u32).to_le_bytes());
for c in &self.layout.columns {
out.extend_from_slice(&c.start.to_le_bytes());
out.extend_from_slice(&c.end.to_le_bytes());
}
out.extend_from_slice(&(self.records.len() as u32).to_le_bytes());
for r in &self.records {
out.extend_from_slice(&r.start.to_le_bytes());
out.extend_from_slice(&r.end.to_le_bytes());
out.extend_from_slice(&(r.fields.len() as u32).to_le_bytes());
for f in &r.fields {
out.extend_from_slice(&f.start.to_le_bytes());
out.extend_from_slice(&f.end.to_le_bytes());
}
}
out
}
pub fn decode(bytes: &[u8]) -> Result<FixedWidthModel> {
let mut r = BinReader::new(bytes);
if r.bytes(4)? != b"FWM1" {
return Err(corrupt("bad model magic"));
}
if r.u8()? != MODEL_VERSION {
return Err(corrupt("unsupported model version"));
}
let bom_len = r.u8()?;
if bom_len > 3 {
return Err(corrupt("implausible BOM length"));
}
let terminator = r.u8()?;
if terminator > TERM_CR {
return Err(corrupt("unknown terminator"));
}
let flags = r.u8()?;
if flags & !3 != 0 {
return Err(corrupt("unknown model flags"));
}
let doc_len = r.u64()?;
let width = r.u64()?;
let ncols = r.u32()?;
if ncols as u64 > MAX_MODEL_FIELDS {
return Err(corrupt("model column count is implausible"));
}
let mut columns = Vec::with_capacity(ncols as usize);
for _ in 0..ncols {
let start = r.u64()?;
let end = r.u64()?;
if start > end || end > width {
return Err(corrupt("column span is outside the record width"));
}
columns.push(Column { start, end });
}
let count = r.u32()?;
if count > MAX_MODEL_RECORDS {
return Err(corrupt("model record count is implausible"));
}
if bom_len as u64 > doc_len {
return Err(corrupt("BOM length exceeds the document"));
}
let mut records = Vec::with_capacity(count as usize);
let mut total_fields = 0u64;
for _ in 0..count {
let start = r.u64()?;
let end = r.u64()?;
if start > end || end > doc_len {
return Err(corrupt("record span is outside the document"));
}
if end - start != width {
return Err(corrupt("record width does not match the layout"));
}
let fn_ = r.u32()?;
total_fields = total_fields.saturating_add(fn_ as u64);
if total_fields > MAX_MODEL_FIELDS {
return Err(corrupt("model field count is implausible"));
}
let mut fields = Vec::with_capacity(fn_ as usize);
for _ in 0..fn_ {
let fs = r.u64()?;
let fe = r.u64()?;
if fs > fe || fs < start || fe > end {
return Err(corrupt("field span is outside its record"));
}
fields.push(FwField { start: fs, end: fe });
}
records.push(FwRecord { start, end, fields });
}
if !r.at_end() {
return Err(corrupt("model has trailing bytes"));
}
Ok(FixedWidthModel {
doc_len,
layout: Layout {
bom_len,
terminator,
mixed_terminators: flags & 2 != 0,
width,
columns,
},
has_header: flags & 1 != 0,
records,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FwMatch {
pub record: u32,
pub column: u32,
pub start: u64,
pub end: u64,
pub text: String,
}
pub fn detect(source: &[u8], limits: Limits) -> bool {
if source.is_empty() {
return false;
}
if source.len() as u64 > limits.max_fixedwidth_document_bytes {
return false;
}
if looks_like_other(source) {
return false;
}
if crate::adapter::csv::detect(source, limits) {
return false;
}
if crate::adapter::csv::has_markdown_delimiter_row(source) {
return false;
}
infer_layout(source, limits).is_ok()
}
fn looks_like_other(source: &[u8]) -> bool {
if source.starts_with(b"%PDF-") {
return true;
}
source.starts_with(b"PK\x03\x04")
|| source.starts_with(b"PK\x05\x06")
|| source.starts_with(b"PK\x07\x08")
}
pub fn infer_layout(source: &[u8], limits: Limits) -> Result<Layout> {
if source.is_empty() {
return Err(decline("empty input is not a fixed-width table"));
}
if source.len() as u64 > limits.max_fixedwidth_document_bytes {
return Err(Error::resource_limit(format!(
"fixed-width source is {} bytes, above the {}-byte document cap",
source.len(),
limits.max_fixedwidth_document_bytes
)));
}
let bom_len = if source.starts_with(&[0xEF, 0xBB, 0xBF]) {
3
} else {
0
};
let cap = limits
.max_fixedwidth_sampled_lines_for_detection
.max(MIN_ROWS as u32) as usize;
let mut starts: Vec<usize> = Vec::with_capacity(cap);
let mut width: Option<usize> = None;
let mut terminator = TERM_LF;
let mut terminator_seen = false;
let mut mixed_terminators = false;
let mut at = bom_len as usize;
while starts.len() < cap {
let (start, content_end, tag, next) = match split_line(source, at) {
Some(x) => x,
None => break,
};
let len = content_end - start;
if len == 0 {
return Err(decline("a blank line is not a fixed-width record"));
}
if len as u64 > limits.max_fixedwidth_record_bytes as u64 {
return Err(Error::resource_limit(format!(
"fixed-width record exceeds the {}-byte cap",
limits.max_fixedwidth_record_bytes
)));
}
match width {
None => width = Some(len),
Some(w) if w != len => {
return Err(decline(
"sampled records do not share an identical byte width",
));
}
Some(_) => {}
}
if !terminator_seen {
terminator = tag;
terminator_seen = true;
} else if tag != terminator {
mixed_terminators = true;
}
starts.push(start);
at = next;
}
if starts.len() < MIN_ROWS {
return Err(decline(
"fewer than the minimum sampled fixed-width records",
));
}
let w = width.expect("a non-empty sample has a width");
let mut sep = vec![false; w];
for (p, s) in sep.iter_mut().enumerate() {
let mut all_space = true;
for &rec_start in &starts {
if source[rec_start + p] != b' ' {
all_space = false;
break;
}
}
*s = all_space;
}
let mut runs: Vec<(bool, usize, usize)> = Vec::new();
let mut p = 0usize;
while p < w {
let s = sep[p];
let st = p;
while p < w && sep[p] == s {
p += 1;
}
runs.push((s, st, p));
}
if !runs.is_empty() && runs[0].0 {
runs.remove(0);
}
if !runs.is_empty() && runs[runs.len() - 1].0 {
runs.pop();
}
let mut columns: Vec<Column> = Vec::new();
for &(is_sep, s, e) in &runs {
if is_sep {
if e - s < MIN_GAP {
return Err(decline(
"a column gap is narrower than the minimum two columns",
));
}
} else {
columns.push(Column {
start: s as u64,
end: e as u64,
});
}
}
if columns.len() < MIN_COLS {
return Err(decline("fewer than two non-empty columns"));
}
if columns.len() as u64 > limits.max_fixedwidth_cols as u64 {
return Err(Error::resource_limit(format!(
"fixed-width table exceeds the {}-column cap",
limits.max_fixedwidth_cols
)));
}
Ok(Layout {
bom_len,
terminator,
mixed_terminators,
width: w as u64,
columns,
})
}
pub fn parse(source: &[u8], limits: Limits, build: bool) -> Result<FixedWidthModel> {
let layout = infer_layout(source, limits)?;
if layout.columns.len() as u64 > limits.max_fixedwidth_cols as u64 {
return Err(Error::resource_limit(format!(
"fixed-width table exceeds the {}-column cap",
limits.max_fixedwidth_cols
)));
}
let mut records: Vec<FwRecord> = Vec::new();
let mut total_fields = 0u64;
let mut at = layout.bom_len as usize;
let mut terminator = TERM_LF;
let mut terminator_seen = false;
let mut mixed_terminators = false;
while let Some((start, content_end, tag, next)) = split_line(source, at) {
let len = content_end - start;
if len as u64 != layout.width {
return Err(corrupt("a record's width differs from the inferred layout"));
}
if !terminator_seen {
terminator = tag;
terminator_seen = true;
} else if tag != terminator {
mixed_terminators = true;
}
total_fields = total_fields.saturating_add(layout.columns.len() as u64);
if total_fields > MAX_MODEL_FIELDS {
return Err(Error::resource_limit(format!(
"fixed-width table exceeds the {MAX_MODEL_FIELDS}-field model cap"
)));
}
if records.len() as u64 >= limits.max_fixedwidth_rows as u64 {
return Err(Error::resource_limit(format!(
"fixed-width table exceeds the {}-record cap",
limits.max_fixedwidth_rows
)));
}
if build {
let mut fields: Vec<FwField> = Vec::with_capacity(layout.columns.len());
for c in &layout.columns {
let s = (c.start as usize).min(len);
let e = (c.end as usize).min(len);
if (e - s) as u64 > limits.max_fixedwidth_field_bytes as u64 {
return Err(Error::resource_limit(format!(
"fixed-width field exceeds the {}-byte cap",
limits.max_fixedwidth_field_bytes
)));
}
fields.push(FwField {
start: (start + s) as u64,
end: (start + e) as u64,
});
}
records.push(FwRecord {
start: start as u64,
end: content_end as u64,
fields,
});
}
at = next;
}
Ok(FixedWidthModel {
doc_len: source.len() as u64,
layout: Layout {
terminator,
mixed_terminators,
..layout
},
has_header: true,
records,
})
}
pub fn build_fixedwidth_model(source: &[u8], limits: Limits) -> Result<Vec<u8>> {
Ok(parse(source, limits, true)?.encode())
}
pub fn record_at(
source: &[u8],
layout: &Layout,
index: u32,
limits: Limits,
) -> Result<StreamRecord> {
if source.len() as u64 > limits.max_fixedwidth_document_bytes {
return Err(Error::resource_limit(format!(
"fixed-width source is {} bytes, above the {}-byte document cap",
source.len(),
limits.max_fixedwidth_document_bytes
)));
}
if index as u64 >= limits.max_fixedwidth_rows as u64 {
return Err(Error::resource_limit(format!(
"fixed-width record index {index} is at or above the {}-record cap",
limits.max_fixedwidth_rows
)));
}
let mut at = layout.bom_len as usize;
let mut i: u32 = 0;
while let Some((start, content_end, tag, next)) = split_line(source, at) {
let len = content_end - start;
if len as u64 != layout.width {
return Err(corrupt("a record's width differs from the inferred layout"));
}
if i == index {
let mut fields: Vec<FwField> = Vec::with_capacity(layout.columns.len());
for c in &layout.columns {
let s = (c.start as usize).min(len);
let e = (c.end as usize).min(len);
fields.push(FwField {
start: (start + s) as u64,
end: (start + e) as u64,
});
}
return Ok(StreamRecord {
start: start as u64,
end: content_end as u64,
terminator: tag,
fields,
});
}
i += 1;
at = next;
}
Err(Error::unsupported_feature(format!(
"fixed-width table has no record {index}"
)))
}
pub fn field_bytes<'a>(source: &'a [u8], field: &FwField) -> Result<&'a [u8]> {
let s = usize::try_from(field.start).map_err(|_| corrupt("span overflow"))?;
let e = usize::try_from(field.end).map_err(|_| corrupt("span overflow"))?;
source
.get(s..e)
.ok_or_else(|| corrupt("field span is outside the source"))
}
pub fn record_bytes<'a>(source: &'a [u8], record: &StreamRecord) -> Result<&'a [u8]> {
let s = usize::try_from(record.start).map_err(|_| corrupt("span overflow"))?;
let e = usize::try_from(record.end).map_err(|_| corrupt("span overflow"))?;
source
.get(s..e)
.ok_or_else(|| corrupt("record span is outside the source"))
}
pub fn decode_field(source: &[u8], field: &FwField) -> Result<String> {
Ok(String::from_utf8_lossy(field_bytes(source, field)?).into_owned())
}
pub fn trimmed_text(source: &[u8], field: &FwField) -> Result<String> {
let raw = field_bytes(source, field)?;
let s = raw.iter().position(|&c| c != b' ').unwrap_or(raw.len());
let e = raw.iter().rposition(|&c| c != b' ').map_or(s, |i| i + 1);
Ok(String::from_utf8_lossy(&raw[s..e]).into_owned())
}
pub fn decode_record_fields(source: &[u8], record: &StreamRecord) -> Result<Vec<String>> {
record
.fields
.iter()
.map(|f| decode_field(source, f))
.collect()
}
pub fn canonical_text(
source: &[u8],
layout: &Layout,
limits: Limits,
max_out: u64,
) -> Result<String> {
if source.len() as u64 > limits.max_fixedwidth_document_bytes {
return Err(Error::resource_limit(format!(
"fixed-width source is {} bytes, above the {}-byte document cap",
source.len(),
limits.max_fixedwidth_document_bytes
)));
}
let mut at = layout.bom_len as usize;
let mut out = String::new();
let mut first = true;
while let Some((start, content_end, _tag, next)) = split_line(source, at) {
let len = content_end - start;
if len as u64 != layout.width {
return Err(corrupt("a record's width differs from the inferred layout"));
}
if !first {
out.push('\n');
}
first = false;
out.push_str(&String::from_utf8_lossy(
source.get(start..content_end).unwrap_or_default(),
));
if out.len() as u64 > max_out {
return Err(Error::resource_limit(format!(
"fixed-width text projection exceeds the {max_out}-byte budget"
)));
}
at = next;
}
Ok(out)
}
pub fn find(
source: &[u8],
layout: &Layout,
pattern: &str,
limits: Limits,
max_out: u64,
) -> Result<Vec<FwMatch>> {
if source.len() as u64 > limits.max_fixedwidth_document_bytes {
return Err(Error::resource_limit(format!(
"fixed-width source is {} bytes, above the {}-byte document cap",
source.len(),
limits.max_fixedwidth_document_bytes
)));
}
let mut at = layout.bom_len as usize;
let mut out: Vec<FwMatch> = Vec::new();
let mut estimated: u64 = 0;
let mut record: u32 = 0;
while let Some((start, content_end, _tag, next)) = split_line(source, at) {
let len = content_end - start;
if len as u64 != layout.width {
return Err(corrupt("a record's width differs from the inferred layout"));
}
for (col, c) in layout.columns.iter().enumerate() {
let s = (c.start as usize).min(len);
let e = (c.end as usize).min(len);
let field = FwField {
start: (start + s) as u64,
end: (start + e) as u64,
};
let text = trimmed_text(source, &field)?;
if text.contains(pattern) {
estimated = estimated.saturating_add(32 + text.len() as u64);
if estimated > max_out {
return Err(Error::resource_limit(format!(
"fixed-width find exceeded the {max_out}-byte budget"
)));
}
out.push(FwMatch {
record,
column: col as u32,
start: field.start,
end: field.end,
text,
});
}
}
record = record.saturating_add(1);
if record as u64 > MAX_MODEL_RECORDS as u64 {
return Err(Error::resource_limit(
"fixed-width find exceeded the record scan cap",
));
}
at = next;
}
Ok(out)
}
fn split_line(b: &[u8], at: usize) -> Option<(usize, usize, u8, usize)> {
if at >= b.len() {
return None;
}
let start = at;
let mut i = at;
while i < b.len() && b[i] != b'\n' && b[i] != b'\r' {
i += 1;
}
if i >= b.len() {
return Some((start, i, TERM_LF, i));
}
if b[i] == b'\r' {
if b.get(i + 1) == Some(&b'\n') {
Some((start, i, TERM_CRLF, i + 2))
} else {
Some((start, i, TERM_CR, i + 1))
}
} else {
Some((start, i, TERM_LF, i + 1))
}
}
fn corrupt(msg: &str) -> Error {
Error::invalid_fixedwidth_structure(format!("malformed fixed-width: {msg}"))
}
fn decline(msg: &str) -> Error {
Error::invalid_fixedwidth_structure(format!("not a fixed-width table: {msg}"))
}
struct BinReader<'a> {
b: &'a [u8],
at: usize,
}
impl<'a> BinReader<'a> {
fn new(b: &'a [u8]) -> Self {
BinReader { b, at: 0 }
}
fn bytes(&mut self, n: usize) -> Result<&'a [u8]> {
let end = self
.at
.checked_add(n)
.ok_or_else(|| corrupt("reader overflow"))?;
let s = self
.b
.get(self.at..end)
.ok_or_else(|| corrupt("reader underrun"))?;
self.at = end;
Ok(s)
}
fn u8(&mut self) -> Result<u8> {
Ok(self.bytes(1)?[0])
}
fn u32(&mut self) -> Result<u32> {
let b = self.bytes(4)?;
Ok(u32::from_le_bytes([b[0], b[1], b[2], b[3]]))
}
fn u64(&mut self) -> Result<u64> {
let b = self.bytes(8)?;
Ok(u64::from_le_bytes([
b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7],
]))
}
fn at_end(&self) -> bool {
self.at == self.b.len()
}
}
const FUSED_BATCH: usize = 4096;
fn trim_ascii(b: &[u8]) -> &[u8] {
let s = b.iter().position(|&c| c != b' ').unwrap_or(b.len());
let e = b.iter().rposition(|&c| c != b' ').map_or(s, |i| i + 1);
&b[s..e]
}
fn parse_i64_trim(raw: &[u8]) -> Result<Option<i64>> {
let s = trim_ascii(raw);
if s.is_empty() {
return Ok(None);
}
let text = std::str::from_utf8(s)
.map_err(|_| Error::unsupported_feature("non-UTF-8 field in a numeric column"))?;
text.parse::<i64>().map(Some).map_err(|_| {
Error::unsupported_feature(format!("fixed-width field {text:?} is not an integer"))
})
}
fn parse_f64_trim(raw: &[u8]) -> Result<Option<f64>> {
let s = trim_ascii(raw);
if s.is_empty() {
return Ok(None);
}
let text = std::str::from_utf8(s)
.map_err(|_| Error::unsupported_feature("non-UTF-8 field in a numeric column"))?;
text.parse::<f64>().map(Some).map_err(|_| {
Error::unsupported_feature(format!("fixed-width field {text:?} is not a number"))
})
}
pub fn fused_scan(
source: &[u8],
column: &str,
kind: crate::adapter::fused::ColumnKind,
plan: &crate::adapter::fused::FusedPlan,
limits: Limits,
) -> Result<crate::adapter::fused::FusedAnswer> {
use crate::adapter::fused::{Accumulator, ColumnKind};
let layout = infer_layout(source, limits)?;
let ncols = layout.columns.len();
let idx = if let Ok(i) = column.parse::<usize>() {
if i < ncols {
i
} else {
return Err(Error::unsupported_feature(format!(
"fixed-width column index {i} is out of range ({ncols} columns)"
)));
}
} else {
let header = record_at(source, &layout, 0, limits)?;
let mut found = None;
for (i, f) in header.fields.iter().enumerate() {
if decode_field(source, f)?.trim() == column {
found = Some(i);
break;
}
}
found.ok_or_else(|| {
Error::unsupported_feature(format!("no fixed-width column named {column:?}"))
})?
};
if plan.op.needs_int_column() && kind != ColumnKind::Int {
return Err(Error::unsupported_feature(
"grouped counts require an integer column",
));
}
let col = layout.columns[idx];
let mut acc = Accumulator::new(plan);
let mut ivals: Vec<i64> = Vec::with_capacity(FUSED_BATCH);
let mut vals: Vec<f64> = Vec::with_capacity(FUSED_BATCH);
let mut mask: Vec<bool> = Vec::with_capacity(FUSED_BATCH);
let mut any_null = false;
let mut rows: u64 = 0;
let mut at = layout.bom_len as usize;
if let Some((_, _, _, next)) = split_line(source, at) {
at = next;
}
while let Some((start, content_end, _tag, next)) = split_line(source, at) {
let len = content_end - start;
if len as u64 != layout.width {
return Err(corrupt("a record's width differs from the inferred layout"));
}
rows += 1;
if rows > limits.max_fixedwidth_rows as u64 {
return Err(Error::resource_limit(format!(
"fixed-width table exceeds the {}-record cap",
limits.max_fixedwidth_rows
)));
}
let s = (col.start as usize).min(len);
let e = (col.end as usize).min(len);
let raw = &source[start + s..start + e];
match kind {
ColumnKind::Int => match parse_i64_trim(raw)? {
Some(v) => {
ivals.push(v);
mask.push(true);
}
None => {
ivals.push(0);
mask.push(false);
any_null = true;
}
},
ColumnKind::Float => match parse_f64_trim(raw)? {
Some(v) => {
vals.push(v);
mask.push(true);
}
None => {
vals.push(0.0);
mask.push(false);
any_null = true;
}
},
}
if mask.len() >= FUSED_BATCH {
let valid = if any_null { Some(&mask[..]) } else { None };
match kind {
ColumnKind::Int => acc.push_i64(&ivals, valid),
ColumnKind::Float => acc.push_f64(&vals, valid),
}
ivals.clear();
vals.clear();
mask.clear();
any_null = false;
}
at = next;
}
if !mask.is_empty() {
let valid = if any_null { Some(&mask[..]) } else { None };
match kind {
ColumnKind::Int => acc.push_i64(&ivals, valid),
ColumnKind::Float => acc.push_f64(&vals, valid),
}
}
Ok(acc.finish(kind))
}
#[cfg(test)]
mod tests {
use super::*;
const TABLE: &[u8] = b"Name Age\nAlice 30\nBob 25\n";
fn layout(source: &[u8]) -> Layout {
infer_layout(source, Limits::DEFAULT).unwrap()
}
#[test]
fn detects_uniform_width_tables_and_rejects_prose() {
assert!(detect(TABLE, Limits::DEFAULT));
let l = layout(TABLE);
assert_eq!(l.columns.len(), 2);
assert_eq!(l.columns[0], Column { start: 0, end: 5 });
assert_eq!(l.columns[1], Column { start: 7, end: 10 });
assert_eq!(l.width, 10);
assert!(!detect(
b"Hello world\nThis is text.\nMore words here.\n",
Limits::DEFAULT
));
assert!(!detect(b"a b\nc d\ne f\n", Limits::DEFAULT));
assert!(!detect(b"Name Age\nAlice 30\n", Limits::DEFAULT));
assert!(!detect(b"a,b\nc,d\n", Limits::DEFAULT));
assert!(!detect(
b"| a | b |\n| --- | --- |\n| c | d |\n",
Limits::DEFAULT
));
assert!(!detect(b"", Limits::DEFAULT));
}
#[test]
fn preserves_spans_padding_and_terminators() {
let src = b"Name Age\r\nAlice 30\r\nBob 25\r\n";
let m = parse(src, Limits::DEFAULT, true).unwrap();
assert_eq!(m.layout.terminator, TERM_CRLF);
assert_eq!(m.records.len(), 3);
assert_eq!(m.records[0].fields.len(), 2);
assert_eq!(field_bytes(src, &m.records[1].fields[0]).unwrap(), b"Alice");
assert_eq!(decode_field(src, &m.records[1].fields[0]).unwrap(), "Alice");
assert_eq!(trimmed_text(src, &m.records[1].fields[0]).unwrap(), "Alice");
assert_eq!(field_bytes(src, &m.records[1].fields[1]).unwrap(), b" 30");
assert_eq!(trimmed_text(src, &m.records[1].fields[1]).unwrap(), "30");
assert_eq!(
&src[m.records[1].start as usize..m.records[1].end as usize],
b"Alice 30"
);
let enc = m.encode();
assert_eq!(FixedWidthModel::decode(&enc).unwrap(), m);
let mut bad = enc.clone();
bad[0] = b'X';
assert!(FixedWidthModel::decode(&bad).is_err());
let mut truncated = enc;
truncated.truncate(truncated.len() - 1);
assert!(FixedWidthModel::decode(&truncated).is_err());
}
#[test]
fn a_shorter_line_declines_the_whole_document() {
let mut src = TABLE.to_vec();
src.extend_from_slice(b"Ted 1\n");
let tight = Limits {
max_fixedwidth_sampled_lines_for_detection: 3,
..Limits::DEFAULT
};
assert!(detect(&src, tight));
let e = parse(&src, tight, true).unwrap_err();
assert_eq!(
e.class(),
crate::error::ErrorClass::InvalidFixedWidthStructure
);
assert!(!detect(&src, Limits::DEFAULT));
}
#[test]
fn caps_decline_typed() {
let tight = Limits {
max_fixedwidth_rows: 1,
..Limits::DEFAULT
};
let e = parse(TABLE, tight, true).unwrap_err();
assert_eq!(e.class(), crate::error::ErrorClass::ResourceLimit);
let tight = Limits {
max_fixedwidth_cols: 1,
..Limits::DEFAULT
};
let e = parse(TABLE, tight, true).unwrap_err();
assert_eq!(e.class(), crate::error::ErrorClass::ResourceLimit);
}
#[test]
fn random_bytes_never_panic() {
let mut x: u64 = 0x9E37_79B9_7F4A_7C15;
for _ in 0..128 {
let mut buf = vec![0u8; 512];
for b in buf.iter_mut() {
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
*b = (x & 0xFF) as u8;
}
let _ = detect(&buf, Limits::DEFAULT);
if let Ok(l) = infer_layout(&buf, Limits::STRICT) {
let _ = canonical_text(&buf, &l, Limits::STRICT, 1 << 20);
let _ = build_fixedwidth_model(&buf, Limits::STRICT);
let _ = find(&buf, &l, "a", Limits::STRICT, 1 << 20);
}
}
}
}