blob: 50f6bdb07cfbc845ebb4740e11e4dba2b525574b [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
use crate::{Error, Fingerprint};
use maka_capabilities::filesystem::PinnedReader;
use maka_capabilities::filesystem::ReadRange;
use serde::Deserialize;
use serde_json::value::RawValue;
use sha2::{Digest, Sha256};
pub(crate) fn decode<'a, T: Deserialize<'a>>(value: &'a RawValue, line: u64) -> Result<T, Error> {
serde_json::from_str(value.get()).map_err(|source| Error::Decode { line, source })
}
pub(crate) struct Line<'a> {
pub number: u64,
pub json: &'a RawValue,
}
impl Line<'_> {
pub fn decode<'a, T: Deserialize<'a>>(&'a self) -> Result<T, Error> {
serde_json::from_str(self.json.get()).map_err(|source| Error::Decode {
line: self.number,
source,
})
}
}
/// All formats get strict interior parsing, chunked reads and a source digest.
/// Only a syntactically unfinished final write is ignored, never a corrupt row.
pub(crate) fn scan(
reader: &mut PinnedReader<'_>,
length: u64,
mut accept: impl FnMut(Line<'_>) -> Result<(), Error>,
) -> Result<Fingerprint, Error> {
let mut digest = Sha256::new();
let mut pending = Vec::new();
let mut offset = 0;
let mut number = 0;
loop {
let page = reader.read(ReadRange {
offset,
limit: 64 * 1024,
})?;
digest.update(&page.bytes);
for segment in page.bytes.split_inclusive(|byte| *byte == b'\n') {
pending.extend_from_slice(segment);
if segment.last() == Some(&b'\n') {
number += 1;
parse(&pending, number, false, &mut accept)?;
pending.clear();
}
}
match page.next_offset {
Some(next) => offset = next,
None => break,
}
}
let incomplete_tail = if pending.is_empty() {
false
} else {
number += 1;
parse(&pending, number, true, &mut accept)?
};
Ok(Fingerprint {
bytes: length,
sha256: hex::encode(digest.finalize()),
incomplete_tail,
})
}
fn parse(
bytes: &[u8],
number: u64,
last: bool,
accept: &mut impl FnMut(Line<'_>) -> Result<(), Error>,
) -> Result<bool, Error> {
if bytes.iter().all(u8::is_ascii_whitespace) {
return Ok(false);
}
match serde_json::from_slice::<&RawValue>(bytes) {
Ok(json) => {
accept(Line { number, json })?;
Ok(false)
}
Err(error) if last && error.is_eof() => Ok(true),
Err(source) => Err(Error::Decode {
line: number,
source,
}),
}
}