use crate::{
any::Any,
containers::{fff_impl, ParsePrompt, ParseStatus},
debug::debug_impl,
Parent,
};
mod error;
pub use error::{ParseAnyDocumentError, ParseDocumentError};
pub struct Document<'json> {
remaining: &'json str,
parse_status: Option<ParseStatus>,
}
impl<'json> Parent<'json> for Document<'json> {
fn set_remaining<'a>(&'a mut self, remaining: &'json str)
where
'json: 'a,
{
self.remaining = remaining;
self.parse_status = Some(ParseStatus::Done);
}
fn debug_parents(&self, list: &mut core::fmt::DebugList<'_, '_>) {
list.entry(&"Document");
}
}
impl<'json> Document<'json> {
#[must_use]
#[inline]
pub const fn new(json: &'json str) -> Self {
Self {
remaining: json,
parse_status: None,
}
}
#[allow(clippy::should_implement_trait)]
pub fn next(&mut self) -> Result<Option<Any<'json, '_>>, ParseDocumentError> {
loop {
let end = match self.parse_status {
None => false,
Some(ParseStatus::Prompted(prompt)) => {
let remaining = self.remaining;
return Ok(Some(prompt.create(self, remaining)));
}
Some(ParseStatus::Done) => true,
};
let Some(c) = self.remaining.chars().next() else {
return if end {
Ok(None)
} else {
Err(ParseDocumentError::UnexpectedEnd)
};
};
if c.is_whitespace() {
} else if end {
return Err(ParseDocumentError::UnexpectedCharacter(c));
} else if let Some(prompt) = ParsePrompt::get(c) {
self.parse_status = Some(prompt.into());
if prompt.keep_first() {
continue;
}
} else {
return Err(ParseDocumentError::InvalidElement(c));
}
self.remaining = &self.remaining[c.len_utf8()..];
}
}
pub fn finish(&mut self) -> Result<(), ParseAnyDocumentError> {
while let Some(mut value) = self.next()? {
value.finish()?;
}
Ok(())
}
fff_impl! {
type: "document"
f(&mut Any<'json, '_>) -> Result<_, ParseAnyDocumentError>;
accumulator, mut value =>
f(&mut value),
f(accumulator, &mut value),
value;
ParseAnyDocumentError::Document
}
}
debug_impl!("Document", Document<'json>, no_parents);
#[cfg(test)]
mod test {
use super::{Document, ParseDocumentError};
#[test]
fn parse_string() {
let expected = "Hello, World!";
let json = format!("\"{expected}\"");
let mut document = Document::new(&json);
let parsed = document
.next()
.expect("failed to parse document")
.expect("got no values in document")
.string()
.expect("expected string from document")
.get()
.expect("failed to parse string");
assert_eq!(parsed, expected);
assert!(document.next().expect("failed to parse document").is_none());
}
#[test]
fn multiple_values() {
let expected = "Hello, World!";
let json = format!("\"{expected}\"\"s2\"");
let mut document = Document::new(&json);
let parsed = document
.next()
.expect("failed to parse document")
.expect("got no values in document")
.string()
.expect("expected string from document")
.get()
.expect("failed to parse string");
assert_eq!(parsed, expected);
let error = document
.next()
.expect_err("failed to return error after parsing invalid document");
assert_eq!(error, ParseDocumentError::UnexpectedCharacter('"'));
}
#[test]
fn empty() {
let error = Document::new("")
.next()
.expect_err("failed to return error after parsing empty document");
assert_eq!(error, ParseDocumentError::UnexpectedEnd);
}
#[test]
fn parse_invalid() {
let invalid = 'j';
let json = invalid.to_string();
let mut document = Document::new(&json);
let error = document
.next()
.expect_err("failed to return error after parsing invalid document");
assert_eq!(error, ParseDocumentError::InvalidElement(invalid));
}
#[test]
fn invalid_after_value() {
let expected = "Hello, World!";
let invalid = 'j';
let json = format!("\"{expected}\"{invalid}");
let mut document = Document::new(&json);
let parsed = document
.next()
.expect("failed to parse document")
.expect("got no values in document")
.string()
.expect("expected string from document")
.get()
.expect("failed to parse string");
assert_eq!(parsed, expected);
let error = document
.next()
.expect_err("failed to return error after parsing invalid document");
assert_eq!(error, ParseDocumentError::UnexpectedCharacter(invalid));
}
}