224 lines
5.8 KiB
Rust
224 lines
5.8 KiB
Rust
use std::path::{Path, PathBuf};
|
|
use std::sync::Arc;
|
|
|
|
use miette::{Diagnostic, NamedSource, SourceSpan};
|
|
use thiserror::Error;
|
|
|
|
pub type Result<T> = core::result::Result<T, Box<Error>>;
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub enum SourceType {
|
|
/// dir
|
|
Eval(Arc<PathBuf>),
|
|
/// dir
|
|
Repl(Arc<PathBuf>),
|
|
/// file
|
|
File(Arc<PathBuf>),
|
|
/// virtual (name, no path)
|
|
Virtual(Arc<str>),
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct Source {
|
|
pub ty: SourceType,
|
|
pub src: Arc<str>,
|
|
}
|
|
|
|
impl TryFrom<&str> for Source {
|
|
type Error = Box<Error>;
|
|
fn try_from(value: &str) -> Result<Self> {
|
|
Source::new_eval(value.into())
|
|
}
|
|
}
|
|
|
|
impl From<Source> for NamedSource<Arc<str>> {
|
|
fn from(value: Source) -> Self {
|
|
let name = value.get_name();
|
|
NamedSource::new(name, value.src.clone())
|
|
}
|
|
}
|
|
|
|
impl Source {
|
|
pub fn new_file(path: PathBuf) -> std::io::Result<Self> {
|
|
Ok(Source {
|
|
src: std::fs::read_to_string(&path)?.into(),
|
|
ty: crate::error::SourceType::File(Arc::new(path)),
|
|
})
|
|
}
|
|
|
|
pub fn new_eval(src: String) -> Result<Self> {
|
|
Ok(Self {
|
|
ty: std::env::current_dir()
|
|
.map_err(|err| Error::internal(format!("Failed to get current working dir: {err}")))
|
|
.map(Arc::new)
|
|
.map(SourceType::Eval)?,
|
|
src: src.into(),
|
|
})
|
|
}
|
|
|
|
pub fn new_repl(src: String) -> Result<Self> {
|
|
Ok(Self {
|
|
ty: std::env::current_dir()
|
|
.map_err(|err| Error::internal(format!("Failed to get current working dir: {err}")))
|
|
.map(Arc::new)
|
|
.map(SourceType::Repl)?,
|
|
src: src.into(),
|
|
})
|
|
}
|
|
|
|
pub fn new_virtual(name: Arc<str>, src: String) -> Self {
|
|
Self {
|
|
ty: SourceType::Virtual(name),
|
|
src: src.into(),
|
|
}
|
|
}
|
|
|
|
pub fn get_dir(&self) -> &Path {
|
|
use SourceType::*;
|
|
match &self.ty {
|
|
Eval(dir) | Repl(dir) => dir.as_ref(),
|
|
File(file) => file
|
|
.as_path()
|
|
.parent()
|
|
.expect("source file must have a parent dir"),
|
|
Virtual(_) => Path::new("/"),
|
|
}
|
|
}
|
|
|
|
pub fn get_name(&self) -> String {
|
|
match &self.ty {
|
|
SourceType::Eval(_) => "«eval»".into(),
|
|
SourceType::Repl(_) => "«repl»".into(),
|
|
SourceType::File(path) => path.as_os_str().to_string_lossy().to_string(),
|
|
SourceType::Virtual(name) => name.to_string(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Error, Debug, Diagnostic)]
|
|
pub enum Error {
|
|
#[error("Parse error: {message}")]
|
|
#[diagnostic(code(nix::parse))]
|
|
ParseError {
|
|
#[source_code]
|
|
src: Option<NamedSource<Arc<str>>>,
|
|
#[label("error occurred here")]
|
|
span: Option<SourceSpan>,
|
|
message: String,
|
|
},
|
|
|
|
#[error("Downgrade error: {message}")]
|
|
#[diagnostic(code(nix::downgrade))]
|
|
DowngradeError {
|
|
#[source_code]
|
|
src: Option<NamedSource<Arc<str>>>,
|
|
#[label("{message}")]
|
|
span: Option<SourceSpan>,
|
|
message: String,
|
|
},
|
|
|
|
#[error("Evaluation error: {message}")]
|
|
#[diagnostic(code(nix::eval))]
|
|
EvalError {
|
|
#[source_code]
|
|
src: Option<NamedSource<Arc<str>>>,
|
|
#[label("error occurred here")]
|
|
span: Option<SourceSpan>,
|
|
message: String,
|
|
#[related]
|
|
stack_trace: Vec<StackFrame>,
|
|
},
|
|
|
|
#[error("Internal error: {message}")]
|
|
#[diagnostic(code(nix::internal))]
|
|
InternalError { message: String },
|
|
|
|
#[error("{message}")]
|
|
#[diagnostic(code(nix::catchable))]
|
|
Catchable { message: String },
|
|
|
|
#[error("Unknown error")]
|
|
#[diagnostic(code(nix::unknown))]
|
|
Unknown,
|
|
}
|
|
|
|
impl Error {
|
|
pub fn parse_error(msg: String) -> Box<Self> {
|
|
Error::ParseError {
|
|
src: None,
|
|
span: None,
|
|
message: msg,
|
|
}
|
|
.into()
|
|
}
|
|
|
|
pub fn downgrade_error(msg: String, src: Source, span: rnix::TextRange) -> Box<Self> {
|
|
Error::DowngradeError {
|
|
src: Some(src.into()),
|
|
span: Some(text_range_to_source_span(span)),
|
|
message: msg,
|
|
}
|
|
.into()
|
|
}
|
|
|
|
pub fn eval_error(msg: impl Into<String>) -> Box<Self> {
|
|
Error::EvalError {
|
|
src: None,
|
|
span: None,
|
|
message: msg.into(),
|
|
stack_trace: Vec::new(),
|
|
}
|
|
.into()
|
|
}
|
|
|
|
pub fn internal(msg: String) -> Box<Self> {
|
|
Error::InternalError { message: msg }.into()
|
|
}
|
|
|
|
pub fn catchable(msg: String) -> Box<Self> {
|
|
Error::Catchable { message: msg }.into()
|
|
}
|
|
|
|
pub fn with_span(mut self: Box<Self>, span: rnix::TextRange) -> Box<Self> {
|
|
use Error::*;
|
|
let source_span = Some(text_range_to_source_span(span));
|
|
let (ParseError { span, .. } | DowngradeError { span, .. } | EvalError { span, .. }) =
|
|
self.as_mut()
|
|
else {
|
|
return self;
|
|
};
|
|
*span = source_span;
|
|
self
|
|
}
|
|
|
|
pub fn with_source(mut self: Box<Self>, source: Source) -> Box<Self> {
|
|
use Error::*;
|
|
let new_src = Some(source.into());
|
|
let (ParseError { src, .. } | DowngradeError { src, .. } | EvalError { src, .. }) =
|
|
self.as_mut()
|
|
else {
|
|
return self;
|
|
};
|
|
*src = new_src;
|
|
self
|
|
}
|
|
}
|
|
|
|
pub fn text_range_to_source_span(range: rnix::TextRange) -> SourceSpan {
|
|
let start = usize::from(range.start());
|
|
let len = usize::from(range.end()) - start;
|
|
SourceSpan::new(start.into(), len)
|
|
}
|
|
|
|
/// Stack frame types from Nix evaluation
|
|
#[derive(Debug, Clone, Error, Diagnostic)]
|
|
#[error("{message}")]
|
|
pub struct StackFrame {
|
|
#[label]
|
|
pub span: SourceSpan,
|
|
#[help]
|
|
pub message: String,
|
|
#[source_code]
|
|
pub src: NamedSource<Arc<str>>,
|
|
}
|