foch 0.0.2

EU4 mod analysis and merge toolkit
Documentation
use std::error::Error;
use std::fmt::{self, Display, Formatter};
use std::path::{Path, PathBuf};

use super::syntax::ByteSpan;

#[derive(Debug)]
pub(crate) enum ParseError {
	Language(String),
	ParseReturnedNone,
}

impl Display for ParseError {
	fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
		match self {
			Self::Language(message) => write!(f, "failed to load paradox language: {message}"),
			Self::ParseReturnedNone => write!(f, "tree-sitter returned no parse tree"),
		}
	}
}

impl Error for ParseError {}

#[derive(Debug)]
pub(crate) enum ProjectionError {
	MissingField {
		node_kind: &'static str,
		field: &'static str,
	},
	UnexpectedNode {
		kind: String,
		span: ByteSpan,
	},
}

impl Display for ProjectionError {
	fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
		match self {
			Self::MissingField { node_kind, field } => {
				write!(f, "missing field `{field}` on `{node_kind}` node")
			}
			Self::UnexpectedNode { kind, span } => write!(
				f,
				"unexpected node `{kind}` at byte span {}..{}",
				span.start, span.end,
			),
		}
	}
}

impl Error for ProjectionError {}

#[derive(Debug)]
pub(crate) enum CwtLoadError {
	Io {
		path: PathBuf,
		source: std::io::Error,
	},
	NoRuleFiles {
		root: PathBuf,
	},
	Syntax(ParseError),
	Projection(ProjectionError),
	InvalidSchema {
		path: Option<PathBuf>,
		message: String,
	},
	Codec {
		message: String,
	},
}

impl CwtLoadError {
	/// Names `file` as the schema file an invalid-schema error came from,
	/// unless the error already names one.
	pub(crate) fn in_file(self, file: &Path) -> Self {
		match self {
			Self::InvalidSchema {
				path: None,
				message,
			} => Self::InvalidSchema {
				path: Some(file.to_path_buf()),
				message,
			},
			other => other,
		}
	}
}

impl Display for CwtLoadError {
	fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
		match self {
			Self::Io { path, source } => {
				write!(f, "failed to read `{}`: {source}", path.display())
			}
			Self::NoRuleFiles { root } => {
				write!(f, "no `.cwt` rule files under `{}`", root.display())
			}
			Self::Syntax(error) => write!(f, "syntax parse failed: {error}"),
			Self::Projection(error) => write!(f, "syntax projection failed: {error}"),
			Self::InvalidSchema { path, message } => {
				if let Some(path) = path {
					write!(f, "invalid schema `{}`: {message}", path.display())
				} else {
					write!(f, "invalid schema: {message}")
				}
			}
			Self::Codec { message } => write!(f, "compiled schema codec failed: {message}"),
		}
	}
}

impl Error for CwtLoadError {
	fn source(&self) -> Option<&(dyn Error + 'static)> {
		match self {
			Self::Io { source, .. } => Some(source),
			Self::Syntax(error) => Some(error),
			Self::Projection(error) => Some(error),
			Self::NoRuleFiles { .. } | Self::InvalidSchema { .. } | Self::Codec { .. } => None,
		}
	}
}

impl From<ParseError> for CwtLoadError {
	fn from(value: ParseError) -> Self {
		Self::Syntax(value)
	}
}

impl From<ProjectionError> for CwtLoadError {
	fn from(value: ProjectionError) -> Self {
		Self::Projection(value)
	}
}