use std::io::Read;
#[non_exhaustive]
pub enum InputSource {
Text(String),
AnchoredText { text: String, anchor: String },
Reader(Box<dyn Read + 'static>),
}
impl std::fmt::Debug for InputSource {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Text(text) => f.debug_tuple("Text").field(text).finish(),
Self::AnchoredText { text, anchor } => f
.debug_struct("AnchoredText")
.field("anchor", anchor)
.field("text", text)
.finish(),
Self::Reader(_) => f.write_str("Reader(..)"),
}
}
}
#[non_exhaustive]
#[derive(Debug)]
pub struct ResolvedInclude {
pub id: String,
pub name: String,
pub source: InputSource,
}
impl ResolvedInclude {
#[must_use]
pub fn new(id: impl Into<String>, name: impl Into<String>, source: InputSource) -> Self {
Self {
id: id.into(),
name: name.into(),
source,
}
}
}
#[derive(Debug)]
#[non_exhaustive]
pub enum ResolveProblem {
ResolveFailed {
spec: String,
base_dir: String,
err: std::io::Error,
},
TargetNotRegularFile { target: String },
TargetIsRootFile { spec: String },
ParentIdNotAbsoluteCanonical { parent_id: String },
ParentResolveFailed {
parent_id: String,
from_name: String,
err: std::io::Error,
},
ParentNotRegularFile { parent: String },
ParentHasNoDirectory { parent: String },
ResolvesOutsideRoot { spec: String, root: String },
TraversesSymlink { spec: String },
AbsolutePathNotAllowed { spec: String },
EmptyPath,
InvalidExtension { spec: String },
HiddenFile { spec: String },
EmptyFragment,
FragmentContainsHash { spec: String },
}
#[derive(Debug)]
#[non_exhaustive]
pub enum IncludeResolveError {
Io(std::io::Error),
Message(String),
SizeLimitExceeded(usize, usize),
FileInclude(Box<ResolveProblem>),
}
impl From<std::io::Error> for IncludeResolveError {
fn from(value: std::io::Error) -> Self {
Self::Io(value)
}
}
#[non_exhaustive]
#[derive(Debug)]
pub struct IncludeRequest<'a> {
pub spec: &'a str,
pub from_name: &'a str,
pub from_id: Option<&'a str>,
pub stack: Vec<String>,
pub size_remaining: Option<usize>,
pub location: crate::Location,
}
impl<'a> IncludeRequest<'a> {
#[must_use]
pub fn new(spec: &'a str, from_name: &'a str, location: crate::Location) -> Self {
Self {
spec,
from_name,
from_id: None,
stack: Vec::new(),
size_remaining: None,
location,
}
}
#[must_use]
pub fn with_from_id(mut self, from_id: &'a str) -> Self {
self.from_id = Some(from_id);
self
}
#[must_use]
pub fn with_stack(mut self, stack: Vec<String>) -> Self {
self.stack = stack;
self
}
#[must_use]
pub fn with_size_remaining(mut self, size_remaining: usize) -> Self {
self.size_remaining = Some(size_remaining);
self
}
}
pub type IncludeResolver<'a> =
dyn FnMut(IncludeRequest<'_>) -> Result<ResolvedInclude, IncludeResolveError> + 'a;
impl InputSource {
#[inline]
#[must_use]
pub fn from_string(s: String) -> Self {
Self::Text(s)
}
#[inline]
#[must_use]
pub fn from_reader<R>(r: R) -> Self
where
R: Read + 'static,
{
Self::Reader(Box::new(r))
}
}
#[cfg(test)]
mod tests {
use super::InputSource;
#[test]
fn debug_formats_each_input_source_variant() {
let text = InputSource::from_string("hello".to_owned());
assert_eq!(format!("{text:?}"), "Text(\"hello\")");
let anchored = InputSource::AnchoredText {
text: "body: true\n".to_owned(),
anchor: "defaults".to_owned(),
};
assert_eq!(
format!("{anchored:?}"),
"AnchoredText { anchor: \"defaults\", text: \"body: true\\n\" }"
);
let reader = InputSource::from_reader(std::io::Cursor::new(b"stream".to_vec()));
assert_eq!(format!("{reader:?}"), "Reader(..)");
}
}