use crate::diagnostic::{Field, ParseWarning, Parsed, WarningCode};
use crate::error::ParseError;
use crate::scrub::{scrub, Scrubbed};
use crate::span::{Located, Span};
use crate::{QuoteStart, RowEntry};
macro_rules! list_type {
($Type:ident, $Entry:ty, non_empty) => {
impl $Type {
pub fn new(entries: Vec<$Entry>) -> Result<Self, $crate::error::ParseError> {
if entries.is_empty() {
return Err($crate::error::ParseError::empty(
$crate::diagnostic::Field::Value,
));
}
Ok(Self(entries))
}
pub fn remove(
&mut self,
index: usize,
) -> Result<Option<$Entry>, $crate::error::ParseError> {
$crate::list::remove_needed(&mut self.0, index)
}
pub fn retain(
&mut self,
keep: impl FnMut(&$Entry) -> bool,
) -> Result<(), $crate::error::ParseError> {
$crate::list::retain_needed(&mut self.0, keep)
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for $Type {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let entries = $crate::serde_parts::shaped::<Vec<$Entry>, _>(
deserializer,
stringify!($Type),
"a sequence",
)?;
Self::new(entries).map_err(<D::Error as serde::de::Error>::custom)
}
}
list_type!(@common $Type, $Entry);
};
($Type:ident, $Entry:ty, may_be_empty) => {
impl $Type {
pub fn new(entries: Vec<$Entry>) -> Self {
Self(entries)
}
pub fn remove(&mut self, index: usize) -> Option<$Entry> {
(index < self.0.len()).then(|| self.0.remove(index))
}
pub fn retain(&mut self, keep: impl FnMut(&$Entry) -> bool) {
self.0
.retain(keep);
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for $Type {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
$crate::serde_parts::shaped::<Vec<$Entry>, _>(
deserializer,
stringify!($Type),
"a sequence",
)
.map(Self::new)
}
}
list_type!(@common $Type, $Entry);
};
(@common $Type:ident, $Entry:ty) => {
#[cfg(feature = "serde")]
impl $crate::list::Entries for $Type {
type Entry = $Entry;
fn into_entry_vec(self) -> Vec<$Entry> {
self.0
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for $Type {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.collect_seq(&self.0)
}
}
impl $Type {
pub fn entries(&self) -> &[$Entry] {
&self.0
}
pub fn into_entries(self) -> Vec<$Entry> {
self.0
}
pub fn iter(&self) -> std::slice::Iter<'_, $Entry> {
self.0
.iter()
}
pub fn iter_mut(&mut self) -> std::slice::IterMut<'_, $Entry> {
self.0
.iter_mut()
}
pub fn get_mut(&mut self, index: usize) -> Option<&mut $Entry> {
self.0
.get_mut(index)
}
pub fn push(&mut self, entry: $Entry) {
self.0
.push(entry);
}
pub fn len(&self) -> usize {
self.0
.len()
}
pub fn is_empty(&self) -> bool {
self.0
.is_empty()
}
}
impl std::fmt::Display for $Type {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
$crate::fmt_joined(f, &self.0, ", ")
}
}
impl IntoIterator for $Type {
type Item = $Entry;
type IntoIter = std::vec::IntoIter<$Entry>;
fn into_iter(self) -> Self::IntoIter {
self.0
.into_iter()
}
}
impl<'a> IntoIterator for &'a $Type {
type Item = &'a $Entry;
type IntoIter = std::slice::Iter<'a, $Entry>;
fn into_iter(self) -> Self::IntoIter {
self.0
.iter()
}
}
};
}
pub(crate) fn remove_needed<T>(
entries: &mut Vec<T>,
index: usize,
) -> Result<Option<T>, ParseError> {
if index >= entries.len() {
return Ok(None);
}
if entries.len() == 1 {
return Err(ParseError::empty(Field::Value));
}
Ok(Some(entries.remove(index)))
}
pub(crate) fn retain_needed<T>(
entries: &mut Vec<T>,
mut keep: impl FnMut(&T) -> bool,
) -> Result<(), ParseError> {
let kept: Vec<bool> = entries
.iter()
.map(&mut keep)
.collect();
if !kept.contains(&true) {
return Err(ParseError::empty(Field::Value));
}
let mut kept = kept.into_iter();
entries.retain(|_| {
kept.next()
.unwrap_or(true)
});
Ok(())
}
fn skipped(fault: &ParseError, entry: Span) -> ParseWarning {
let field = match fault {
ParseError::Malformed(f) => f.field,
ParseError::NonConformant(w) => w.field,
ParseError::Uri(_) | ParseError::Row(_) => Field::Entry,
};
let at = fault
.span()
.map_or(
entry
.range()
.start,
|s| {
s.range()
.start
},
);
ParseWarning::new(field, WarningCode::SkippedEntry)
.at(at)
.covering(entry)
}
pub(crate) trait CommaList: Sized {
type Entry: Located;
const BLANK_ENTRIES_ARE_EMPTY: bool = false;
const QUOTE_START: QuoteStart = QuoteStart::Param;
fn parse_entry(
entry: &str,
warnings: &mut Vec<ParseWarning>,
) -> Result<Self::Entry, ParseError>;
fn from_parsed(entries: Vec<Self::Entry>) -> Result<Self, ParseError>;
fn from_parsed_reporting(
entries: Vec<(usize, Self::Entry)>,
_warnings: &mut Vec<ParseWarning>,
) -> Result<Self, ParseError> {
Self::from_parsed(
entries
.into_iter()
.map(|(_, entry)| entry)
.collect(),
)
}
fn list_from_str(raw: &str) -> Result<Parsed<Self>, ParseError> {
let whole = scrub(raw);
if whole
.text
.trim()
.is_empty()
{
return Self::from_parsed(Vec::new()).map(|v| Parsed::new(v, whole.warnings));
}
Self::list_from_marked(crate::split_row(raw, None, Self::QUOTE_START))
}
fn list_from_entries<'a>(
entries: impl IntoIterator<Item = &'a str>,
) -> Result<Parsed<Self>, ParseError> {
Self::list_from_marked(
entries
.into_iter()
.enumerate()
.map(|(i, e)| RowEntry::whole(i, e)),
)
}
fn list_from_rows<'a>(
rows: impl IntoIterator<Item = &'a str>,
) -> Result<Parsed<Self>, ParseError> {
Self::list_from_marked(crate::row_entries(rows, Self::QUOTE_START))
}
fn list_from_marked<'a>(
entries: impl IntoIterator<Item = RowEntry<'a>>,
) -> Result<Parsed<Self>, ParseError> {
let ReadEntries {
kept,
mut warnings,
first_fault,
rows,
} = Self::read_marked(entries)?;
let none_kept = kept.is_empty();
let value =
Self::from_parsed_reporting(kept, &mut warnings).map_err(|e| match first_fault {
Some(fault) if none_kept => fault,
_ => e,
})?;
for w in &mut warnings {
if w.row
.is_none()
{
w.row = w
.entry
.and_then(|e| rows.get(e))
.copied()
.flatten();
}
}
Ok(Parsed::new(value, warnings))
}
fn read_marked<'a>(
entries: impl IntoIterator<Item = RowEntry<'a>>,
) -> Result<ReadEntries<Self::Entry>, ParseError> {
let entries: Vec<(RowEntry<'a>, Scrubbed<'a>)> = entries
.into_iter()
.map(|e| (e, scrub(e.text)))
.collect();
let is_blank = |e: &Scrubbed<'_>| {
e.text
.trim()
.is_empty()
};
let empty_list = Self::BLANK_ENTRIES_ARE_EMPTY
&& matches!(entries.as_slice(), [(e, s)] if e.comma.is_none() && is_blank(s));
let rows: Vec<Option<usize>> = entries
.iter()
.map(|(e, _)| e.row)
.collect();
let mut warnings = Vec::new();
let mut kept = Vec::with_capacity(entries.len());
let mut first_fault = None;
for (i, (entry, mut scrubbed)) in entries
.into_iter()
.enumerate()
{
let in_row = entry.relocation();
let comma = entry.final_comma();
let own: Vec<ParseWarning> = std::mem::take(&mut scrubbed.warnings)
.into_iter()
.map(|w| w.relocate(&in_row))
.collect();
if is_blank(&scrubbed) {
let empty =
(!empty_list).then(|| crate::empty_entry(Field::Entry, 0).relocate(&in_row));
warnings.extend(
own.into_iter()
.chain(empty)
.chain(comma)
.map(|w| w.in_entry(i)),
);
continue;
}
let back = scrubbed
.relocation()
.then_shift(entry.base, entry.row);
let mut found = Vec::new();
let whole = Span::within(
&scrubbed.text,
scrubbed
.text
.trim(),
);
let value = match Self::parse_entry(&scrubbed.text, &mut found) {
Ok(value) => Some(value),
Err(ParseError::Row(e)) => return Err(ParseError::Row(e)),
Err(e) => {
let e = e.placed(
whole
.range()
.start,
);
found.push(skipped(&e, whole));
first_fault.get_or_insert_with(|| {
e.relocate(&back)
.in_entry(i)
});
None
}
};
let found = found
.into_iter()
.map(|w| w.relocate(&back));
warnings.extend(
own.into_iter()
.chain(found)
.chain(comma)
.map(|w| w.in_entry(i)),
);
kept.extend(value.map(|mut v| {
v.relocate_spans(&back);
(i, v)
}));
}
Ok(ReadEntries {
kept,
warnings,
first_fault,
rows,
})
}
}
pub(crate) struct ReadEntries<E> {
kept: Vec<(usize, E)>,
warnings: Vec<ParseWarning>,
first_fault: Option<ParseError>,
rows: Vec<Option<usize>>,
}
pub(crate) fn entry_from_str<L: CommaList>(raw: &str) -> Result<Parsed<L::Entry>, ParseError> {
let mut split = crate::split_row(raw, None, L::QUOTE_START);
let first = split.next();
let rest = split
.next()
.and(first)
.and_then(|e| {
e.base
.map(|b| {
b + e
.text
.len()
})
});
let read = L::read_marked(first)?;
let Some((_, value)) = read
.kept
.into_iter()
.next()
else {
return Err(read
.first_fault
.map_or_else(
|| ParseError::empty(Field::Value),
ParseError::without_entry,
));
};
let mut warnings = read.warnings;
for w in &mut warnings {
w.entry = None;
}
if let Some(at) = rest {
warnings.push(
ParseWarning::new(Field::Value, WarningCode::TrailingContent)
.at(at)
.covering(Span::new(
at..raw
.trim_end()
.len(),
)),
);
}
Ok(Parsed::new(value, warnings))
}
macro_rules! entry_parse {
($Entry:ty, $List:ty) => {
impl $crate::traits::HeaderParse for $Entry {
fn parse_with_warnings(
raw: &str,
) -> Result<$crate::diagnostic::Parsed<Self>, $crate::error::ParseError> {
$crate::list::entry_from_str::<$List>(raw)
}
}
};
}
#[cfg(feature = "serde")]
pub(crate) trait Entries {
type Entry;
fn into_entry_vec(self) -> Vec<Self::Entry>;
}
#[cfg(feature = "serde")]
pub(crate) fn entry_reads_back<L>(
entry: <L as CommaList>::Entry,
) -> Result<<L as CommaList>::Entry, ParseError>
where
L: CommaList + Entries<Entry = <L as CommaList>::Entry>,
<L as CommaList>::Entry: std::fmt::Display + PartialEq,
{
crate::check::reads_back(entry, |wire| {
let mut entries = L::list_from_str(wire)?
.value
.into_entry_vec();
match (entries.pop(), entries.is_empty()) {
(Some(one), true) => Ok(one),
_ => Err(ParseError::malformed(
crate::diagnostic::Field::Value,
crate::error::FaultCode::Unrepresentable,
None,
)),
}
})
}
macro_rules! list_parse {
($Type:ident) => {
impl $crate::traits::sealed::Sealed for $Type {}
impl $crate::traits::HeaderParse for $Type {
fn parse_with_warnings(
raw: &str,
) -> Result<$crate::diagnostic::Parsed<Self>, $crate::error::ParseError> {
<Self as $crate::list::CommaList>::list_from_str(raw)
}
}
impl $crate::traits::ListParse for $Type {
fn from_entries_with_warnings<'a>(
entries: impl IntoIterator<Item = &'a str>,
) -> Result<$crate::diagnostic::Parsed<Self>, $crate::error::ParseError> {
<Self as $crate::list::CommaList>::list_from_entries(entries)
}
fn from_rows_with_warnings<'a>(
rows: impl IntoIterator<Item = &'a str>,
) -> Result<$crate::diagnostic::Parsed<Self>, $crate::error::ParseError> {
<Self as $crate::list::CommaList>::list_from_rows(rows)
}
}
};
}
#[cfg(test)]
pub(crate) mod testing {
use std::fmt::Debug;
use sip_uri::WarningKind;
use crate::diagnostic::{Field, ParseWarning, WarningCode};
use crate::error::ParseError;
use crate::HeaderParse;
pub(crate) type Seen = (
Field,
WarningCode,
WarningKind,
Option<usize>,
Option<usize>,
);
fn seen_in(warnings: &[ParseWarning]) -> Vec<Seen> {
warnings
.iter()
.map(|w| (w.field, w.code, w.kind, w.position, w.entry))
.collect()
}
pub(crate) fn seen<T: HeaderParse>(raw: &str) -> Vec<Seen> {
seen_in(
&T::parse_with_warnings(raw)
.unwrap()
.warnings,
)
}
pub(crate) fn lenient<T>(raw: &str) -> (T, Vec<Seen>)
where
T: HeaderParse + Debug + PartialEq,
{
assert!(
matches!(T::parse_strict(raw), Err(ParseError::NonConformant(_))),
"{raw}"
);
let parsed = T::parse_with_warnings(raw).unwrap();
assert_eq!(T::parse(raw).as_ref(), Ok(&parsed.value));
let seen = seen_in(&parsed.warnings);
(parsed.value, seen)
}
}