Skip to content

Commit

Permalink
Wrap value in SubfieldValue
Browse files Browse the repository at this point in the history
Signed-off-by: Nico Wagner <[email protected]>
  • Loading branch information
nwagner84 committed Sep 2, 2024
1 parent 344d126 commit b2ed5d1
Show file tree
Hide file tree
Showing 4 changed files with 235 additions and 23 deletions.
2 changes: 1 addition & 1 deletion crates/pica-path/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,7 @@ impl<'a> PathExt for RecordRef<'a> {
.flat_map(FieldRef::subfields)
.filter_map(|subfield| {
if path.codes_flat().contains(subfield.code()) {
Some(subfield.value())
Some(subfield.value().as_bstr())
} else {
None
}
Expand Down
3 changes: 3 additions & 0 deletions pica-record/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ use thiserror::Error;
pub enum PicaError {
#[error("'{0}' is not a valid subfield code.")]
InvalidSubfieldCode(char),

#[error("'{0}' is not a valid subfield value.")]
InvalidSubfieldValue(String),
}

/// -----{ TODO }-----------------------------------------
Expand Down
12 changes: 10 additions & 2 deletions pica-record/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,17 @@
//! select).

pub use error::PicaError;
pub use subfield::SubfieldCode;
pub use subfield::{SubfieldCode, SubfieldValue, SubfieldValueRef};

/// Parsers recognizing low-level primitives (e.g. subfield codes).
#[rustfmt::skip]
pub mod parser_v2 {
pub use super::subfield::parse_subfield_code;
pub use super::subfield::parse_subfield_value_ref;
}

// -----{ TODO }-----------------------------------------

/// -----{ TODO }-----------------------------------------
mod error;
mod field;
pub mod io;
Expand Down
241 changes: 221 additions & 20 deletions pica-record/src/subfield.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
//! subfields.

use std::fmt::{self, Display};
use std::ops::Deref;

use bstr::BStr;

use crate::PicaError;

Expand Down Expand Up @@ -122,6 +125,215 @@ pub fn parse_subfield_code(i: &mut &[u8]) -> PResult<SubfieldCode> {
.parse_next(i)
}

/// An immutable PICA+ subfield value.
///
/// This type behaves like byte slice but guarantees that the subfield
/// value does not contain neither '\x1e' or '\x1f'.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Hash)]
pub struct SubfieldValueRef<'a>(&'a [u8]);

impl<'a> SubfieldValueRef<'a> {
/// Create a new subfield value reference from a byte slice.
///
/// # Error
///
/// This function fails if the subfield value contains either the
/// field separator '\x1f' or the record separator '\x1e'.
///
/// # Example
///
/// ```rust
/// use pica_record::SubfieldValueRef;
///
/// let value = SubfieldValueRef::new(b"abc")?;
/// assert_eq!(value, "abc");
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
pub fn new<T>(value: &'a T) -> Result<Self, PicaError>
where
T: AsRef<[u8]>,
{
let value = value.as_ref();
if value.find_byteset(b"\x1f\x1e").is_some() {
return Err(PicaError::InvalidSubfieldValue(
value.to_str_lossy().to_string(),
));
}

Ok(Self(value))
}

/// Create a new subfield value reference from a byte slice without
/// checking for validity.
///
/// # Safety
///
/// The caller *must* ensure that the value neither contains the
/// record separator '\x1e' nor the field separator '\x1f'.
///
/// # Example
///
/// ```rust
/// use pica_record::SubfieldValueRef;
///
/// let value = SubfieldValueRef::from_unchecked("abc");
/// assert_eq!(value, "abc");
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
pub fn from_unchecked<T>(value: &'a T) -> Self
where
T: AsRef<[u8]> + ?Sized,
{
Self(value.as_ref())
}
}

impl<'a> Deref for SubfieldValueRef<'a> {
type Target = BStr;

fn deref(&self) -> &Self::Target {
self.0.as_bstr()
}
}

impl Display for SubfieldValueRef<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0.as_bstr())
}
}

impl PartialEq<str> for SubfieldValueRef<'_> {
fn eq(&self, value: &str) -> bool {
self.0 == value.as_bytes()
}
}

impl PartialEq<&str> for SubfieldValueRef<'_> {
fn eq(&self, value: &&str) -> bool {
self.0 == value.as_bytes()
}
}

impl PartialEq<Vec<u8>> for SubfieldValueRef<'_> {
fn eq(&self, other: &Vec<u8>) -> bool {
self.0 == other
}
}

/// Parse a PICA+ subfield value reference.
pub fn parse_subfield_value_ref<'a>(
i: &mut &'a [u8],
) -> PResult<SubfieldValueRef<'a>> {
take_till(0.., |c| c == b'\x1f' || c == b'\x1e')
.map(SubfieldValueRef)
.parse_next(i)
}

/// A mutable PICA+ subfield value.
///
/// This type behaves like byte slice but guarantees that the subfield
/// value does not contain neither '\x1e' or '\x1f'.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Hash)]
pub struct SubfieldValue(Vec<u8>);

impl SubfieldValue {
/// Create a new subfield value from a byte slice.
///
/// # Error
///
/// This function fails if the subfield value contains either the
/// field separator '\x1f' or the record separator '\x1e'.
///
/// # Example
///
/// ```rust
/// use pica_record::SubfieldValue;
///
/// let value = SubfieldValue::new(b"abc")?;
/// assert_eq!(value, "abc");
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
pub fn new<T>(value: &T) -> Result<Self, PicaError>
where
T: AsRef<[u8]>,
{
let value = value.as_ref();
if value.find_byteset(b"\x1f\x1e").is_some() {
return Err(PicaError::InvalidSubfieldValue(
value.to_str_lossy().to_string(),
));
}

Ok(Self(value.to_vec()))
}

/// Create a new subfield value from a byte slice without checking
/// for validity.
///
/// # Safety
///
/// The caller *must* ensure that the value neither contains the
/// record separator '\x1e' nor the field separator '\x1f'.
///
/// # Example
///
/// ```rust
/// use pica_record::SubfieldValue;
///
/// let value = SubfieldValue::from_unchecked("abc");
/// assert_eq!(value, "abc");
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
pub fn from_unchecked<T>(value: &T) -> Self
where
T: AsRef<[u8]> + ?Sized,
{
Self(value.as_ref().to_vec())
}
}

impl Display for SubfieldValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0.as_bstr())
}
}

impl From<SubfieldValueRef<'_>> for SubfieldValue {
fn from(value: SubfieldValueRef<'_>) -> Self {
Self(value.to_vec())
}
}

impl PartialEq<SubfieldValueRef<'_>> for SubfieldValue {
fn eq(&self, other: &SubfieldValueRef<'_>) -> bool {
self.0 == other.0
}
}

impl PartialEq<SubfieldValue> for SubfieldValueRef<'_> {
fn eq(&self, other: &SubfieldValue) -> bool {
self.0 == other.0
}
}

impl PartialEq<&str> for SubfieldValue {
fn eq(&self, other: &&str) -> bool {
self.0 == other.as_bytes()
}
}

#[cfg(feature = "arbitrary")]
impl quickcheck::Arbitrary for SubfieldValue {
fn arbitrary(g: &mut quickcheck::Gen) -> Self {
let value = String::arbitrary(g).replace(['\x1f', '\x1e'], "");
Self::from_unchecked(&value)
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down Expand Up @@ -202,7 +414,7 @@ use std::io::{self, Write};
use std::iter;
use std::str::Utf8Error;

use bstr::{BStr, BString, ByteSlice};
use bstr::ByteSlice;
use winnow::combinator::preceded;
use winnow::token::{one_of, take_till};
use winnow::{PResult, Parser};
Expand All @@ -213,30 +425,22 @@ use crate::error::ParsePicaError;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SubfieldRef<'a> {
code: SubfieldCode,
value: &'a BStr,
value: SubfieldValueRef<'a>,
}

/// A mutable PICA+ subfield.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Subfield {
code: SubfieldCode,
value: BString,
}

/// Parse a PICA+ subfield value.
#[inline]
fn parse_subfield_value<'a>(i: &mut &'a [u8]) -> PResult<&'a BStr> {
take_till(0.., |c| c == b'\x1f' || c == b'\x1e')
.map(ByteSlice::as_bstr)
.parse_next(i)
value: SubfieldValue,
}

/// Parse a PICA+ subfield.
#[inline]
pub(crate) fn parse_subfield<'a>(
i: &mut &'a [u8],
) -> PResult<SubfieldRef<'a>> {
preceded(b'\x1f', (parse_subfield_code, parse_subfield_value))
preceded(b'\x1f', (parse_subfield_code, parse_subfield_value_ref))
.map(|(code, value)| SubfieldRef { code, value })
.parse_next(i)
}
Expand Down Expand Up @@ -329,8 +533,8 @@ impl<'a> SubfieldRef<'a> {
/// Ok(())
/// }
/// ```
pub fn value(&self) -> &BStr {
self.value
pub fn value(&self) -> &SubfieldValueRef {
&self.value
}

/// Returns true if the subfield value is empty.
Expand Down Expand Up @@ -380,7 +584,7 @@ impl<'a> SubfieldRef<'a> {
return Ok(());
}

std::str::from_utf8(self.value)?;
std::str::from_utf8(&self.value)?;
Ok(())
}

Expand Down Expand Up @@ -458,7 +662,7 @@ where

Ok(Self {
code: SubfieldCode(code),
value,
value: SubfieldValueRef::from_unchecked(value),
})
}
}
Expand Down Expand Up @@ -521,12 +725,9 @@ impl From<SubfieldRef<'_>> for Subfield {
#[cfg(feature = "arbitrary")]
impl quickcheck::Arbitrary for Subfield {
fn arbitrary(g: &mut quickcheck::Gen) -> Self {
let value =
String::arbitrary(g).replace(['\x1f', '\x1e'], "").into();

Self {
code: SubfieldCode::arbitrary(g),
value,
value: SubfieldValue::arbitrary(g),
}
}
}
Expand Down

0 comments on commit b2ed5d1

Please sign in to comment.