use alloc::{string::String, vec::Vec};
use bytes::Bytes;
use core::{
fmt,
hash::{Hash, Hasher},
ops::{Deref, Range},
};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum JsonlLineError {
EmbeddedLf { offset: usize },
InvalidRange {
start: usize,
end: usize,
len: usize,
},
}
impl fmt::Display for JsonlLineError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::EmbeddedLf { offset } => {
write!(f, "JSONL line contains an embedded LF at byte {offset}")
},
Self::InvalidRange { start, end, len } => write!(
f,
"JSONL line range {start}..{end} is outside a {len}-byte buffer"
),
}
}
}
impl core::error::Error for JsonlLineError {}
#[derive(Clone, Debug)]
pub struct OwnedJsonlLine(Vec<u8>);
impl OwnedJsonlLine {
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
pub fn into_vec(self) -> Vec<u8> {
self.0
}
}
#[derive(Clone)]
#[repr(transparent)]
pub struct SharedJsonlLine(Bytes);
impl fmt::Debug for SharedJsonlLine {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("SharedJsonlLine")
.field(&self.as_bytes())
.finish()
}
}
impl SharedJsonlLine {
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
pub fn into_bytes(self) -> Bytes {
self.0
}
}
#[derive(Clone, Debug)]
pub enum JsonlLine {
Owned(OwnedJsonlLine),
Shared(SharedJsonlLine),
}
impl JsonlLine {
pub fn owned(bytes: Vec<u8>) -> Result<Self, JsonlLineError> {
validate(&bytes)?;
Ok(Self::Owned(OwnedJsonlLine(bytes)))
}
pub fn shared(bytes: Bytes) -> Result<Self, JsonlLineError> {
validate(&bytes)?;
Ok(Self::Shared(SharedJsonlLine(bytes)))
}
pub fn shared_slice(backing: Bytes, range: Range<usize>) -> Result<Self, JsonlLineError> {
let bytes = backing
.get(range.clone())
.ok_or(JsonlLineError::InvalidRange {
start: range.start,
end: range.end,
len: backing.len(),
})?;
validate(bytes)?;
Ok(Self::Shared(SharedJsonlLine(backing.slice(range))))
}
pub fn copy_from_slice(bytes: &[u8]) -> Result<Self, JsonlLineError> {
validate(bytes)?;
Ok(Self::Owned(OwnedJsonlLine(bytes.to_vec())))
}
pub fn as_bytes(&self) -> &[u8] {
match self {
Self::Owned(line) => line.as_bytes(),
Self::Shared(line) => line.as_bytes(),
}
}
pub fn content(&self) -> &[u8] {
let bytes = self.as_bytes();
match bytes.strip_suffix(b"\n") {
Some(content) => content.strip_suffix(b"\r").unwrap_or(content),
None => bytes,
}
}
pub fn is_terminated(&self) -> bool {
self.as_bytes().ends_with(b"\n")
}
pub fn len(&self) -> usize {
self.as_bytes().len()
}
pub fn is_empty(&self) -> bool {
self.as_bytes().is_empty()
}
pub fn into_vec(self) -> Vec<u8> {
match self {
Self::Owned(line) => line.into_vec(),
Self::Shared(line) => line.as_bytes().to_vec(),
}
}
pub fn into_bytes(self) -> Bytes {
match self {
Self::Owned(line) => Bytes::from(line.into_vec()),
Self::Shared(line) => line.into_bytes(),
}
}
pub fn into_shared(self) -> Self {
match self {
Self::Shared(_) => self,
Self::Owned(line) => Self::Shared(SharedJsonlLine(Bytes::from(line.into_vec()))),
}
}
pub fn into_compact(self) -> Self {
Self::Owned(OwnedJsonlLine(self.as_bytes().to_vec()))
}
pub(crate) fn framed(bytes: Bytes) -> Self {
debug_assert!(validate(&bytes).is_ok());
Self::Shared(SharedJsonlLine(bytes))
}
}
fn validate(bytes: &[u8]) -> Result<(), JsonlLineError> {
if let Some(offset) = memchr::memchr(b'\n', bytes) {
if offset + 1 != bytes.len() {
return Err(JsonlLineError::EmbeddedLf { offset });
}
}
Ok(())
}
impl TryFrom<Vec<u8>> for JsonlLine {
type Error = JsonlLineError;
fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
Self::owned(bytes)
}
}
impl TryFrom<Bytes> for JsonlLine {
type Error = JsonlLineError;
fn try_from(bytes: Bytes) -> Result<Self, Self::Error> {
Self::shared(bytes)
}
}
impl TryFrom<String> for JsonlLine {
type Error = JsonlLineError;
fn try_from(text: String) -> Result<Self, Self::Error> {
Self::owned(text.into_bytes())
}
}
impl AsRef<[u8]> for JsonlLine {
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
impl Deref for JsonlLine {
type Target = [u8];
fn deref(&self) -> &Self::Target {
self.as_bytes()
}
}
impl PartialEq for JsonlLine {
fn eq(&self, other: &Self) -> bool {
self.as_bytes() == other.as_bytes()
}
}
impl Eq for JsonlLine {}
impl Hash for JsonlLine {
fn hash<H: Hasher>(&self, state: &mut H) {
self.as_bytes().hash(state);
}
}
#[cfg(all(test, feature = "std"))]
mod tests {
use super::*;
use alloc::{format, vec};
use std::{
collections::hash_map::DefaultHasher,
sync::{
Arc,
atomic::{AtomicBool, Ordering},
},
};
#[test]
fn constructors_check_framing_in_both_storage_modes() {
for (bytes, offset) in [
(b"\nx".as_slice(), 0),
(b"a\nb", 1),
(b"\n\n", 0),
(b"a\r\nb", 2),
(b"a\n\r\n", 1),
] {
let expected = JsonlLineError::EmbeddedLf { offset };
assert_eq!(JsonlLine::owned(bytes.to_vec()).unwrap_err(), expected);
assert_eq!(
JsonlLine::shared(Bytes::copy_from_slice(bytes)).unwrap_err(),
expected
);
assert_eq!(JsonlLine::copy_from_slice(bytes).unwrap_err(), expected);
}
assert!(JsonlLine::try_from(String::from("first\nsecond")).is_err());
}
#[test]
fn valid_lines_preserve_bytes_content_and_terminators() {
for (bytes, content, terminated) in [
(b"".as_slice(), b"".as_slice(), false),
(b"\n", b"", true),
(b"\r\n", b"", true),
(b"{}", b"{}", false),
(b"{}\n", b"{}", true),
(b"{}\r\n", b"{}", true),
(b"a\rb\r", b"a\rb\r", false),
(b" \t{}\t \r\n", b" \t{}\t ", true),
(b"\xff\n", b"\xff", true),
] {
let owned = JsonlLine::owned(bytes.to_vec()).unwrap();
let shared = JsonlLine::shared(Bytes::copy_from_slice(bytes)).unwrap();
assert_eq!(owned, shared);
for line in [owned, shared] {
assert_eq!(line.as_bytes(), bytes);
assert_eq!(line.content(), content);
assert_eq!(line.is_terminated(), terminated);
assert_eq!(line.len(), bytes.len());
assert_eq!(line.is_empty(), bytes.is_empty());
}
}
}
#[test]
fn shared_ranges_are_checked_and_errors_are_relative_to_the_line() {
assert_eq!(
core::mem::size_of::<SharedJsonlLine>(),
core::mem::size_of::<Bytes>()
);
let backing = Bytes::from_static(b"prefix\n{}\r\nsuffix\n");
let line = JsonlLine::shared_slice(backing.clone(), 7..11).unwrap();
let JsonlLine::Shared(payload) = &line else {
unreachable!()
};
assert_eq!(payload.0.len(), 4, "Bytes itself is the exact line view");
assert_eq!(payload.0.as_ptr(), backing[7..11].as_ptr());
assert_eq!(line.content(), b"{}");
assert_eq!(line.clone().into_bytes().as_ref(), b"{}\r\n");
assert_eq!(
JsonlLine::shared_slice(backing.clone(), 7..12).unwrap_err(),
JsonlLineError::EmbeddedLf { offset: 3 }
);
for range in [9..8, 0..backing.len() + 1, usize::MAX..usize::MAX] {
assert_eq!(
JsonlLine::shared_slice(backing.clone(), range.clone()).unwrap_err(),
JsonlLineError::InvalidRange {
start: range.start,
end: range.end,
len: backing.len()
}
);
}
let debug = format!("{line:?}");
assert_eq!(debug, "Shared(SharedJsonlLine([123, 125, 13, 10]))");
}
#[test]
fn ownership_conversions_and_clones_have_documented_copy_behavior() {
let mut data = Vec::with_capacity(1024);
data.extend_from_slice(b"{}\n");
let pointer = data.as_ptr();
let owned = JsonlLine::owned(data).unwrap();
let copied = owned.clone();
assert_ne!(copied.as_bytes().as_ptr(), pointer);
let shared = owned.into_shared();
assert!(matches!(shared, JsonlLine::Shared(_)));
assert_eq!(shared.as_bytes().as_ptr(), pointer);
let clone = shared.clone();
assert_eq!(clone.as_bytes().as_ptr(), pointer);
assert_eq!(shared, copied);
let hash = |line: &JsonlLine| {
let mut h = DefaultHasher::new();
line.hash(&mut h);
h.finish()
};
assert_eq!(hash(&shared), hash(&copied));
assert_eq!(shared.into_bytes().as_ptr(), pointer);
assert_eq!(clone.into_vec(), b"{}\n");
assert_ne!(JsonlLine::owned(b"{}".to_vec()).unwrap(), copied);
}
struct Owner {
bytes: Vec<u8>,
dropped: Arc<AtomicBool>,
}
impl AsRef<[u8]> for Owner {
fn as_ref(&self) -> &[u8] {
&self.bytes
}
}
impl Drop for Owner {
fn drop(&mut self) {
self.dropped.store(true, Ordering::SeqCst);
}
}
#[test]
fn compact_line_releases_a_large_backing_owner() {
let dropped = Arc::new(AtomicBool::new(false));
let backing = Bytes::from_owner(Owner {
bytes: vec![b'x'; 256 * 1024],
dropped: dropped.clone(),
});
let line = JsonlLine::shared_slice(backing, 123..127).unwrap();
assert!(!dropped.load(Ordering::SeqCst));
let line = line.into_compact();
assert!(dropped.load(Ordering::SeqCst));
let JsonlLine::Owned(line) = line else {
panic!("compact lines use owned storage")
};
let bytes = line.into_vec();
assert_eq!(bytes, b"xxxx");
assert_eq!(bytes.capacity(), bytes.len());
}
}