use std::path::{Path, PathBuf}; use std::sync::Arc; use miette::{Diagnostic, NamedSource, SourceSpan}; use thiserror::Error; pub type Result = core::result::Result>; #[derive(Clone, Debug)] pub enum SourceType { /// dir Eval(Arc), /// dir Repl(Arc), /// file File(Arc), /// virtual (name, no path) Virtual(Arc), } #[derive(Clone, Debug)] pub struct Source { pub ty: SourceType, pub src: Arc, } impl TryFrom<&str> for Source { type Error = Box; fn try_from(value: &str) -> Result { Source::new_eval(value.into()) } } impl From for NamedSource> { 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 { 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 { 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 { 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, 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>>, #[label("error occurred here")] span: Option, message: String, }, #[error("Downgrade error: {message}")] #[diagnostic(code(nix::downgrade))] DowngradeError { #[source_code] src: Option>>, #[label("{message}")] span: Option, message: String, }, #[error("Evaluation error: {message}")] #[diagnostic(code(nix::eval))] EvalError { #[source_code] src: Option>>, #[label("error occurred here")] span: Option, message: String, #[related] stack_trace: Vec, }, #[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 { Error::ParseError { src: None, span: None, message: msg, } .into() } pub fn downgrade_error(msg: String, src: Source, span: rnix::TextRange) -> Box { Error::DowngradeError { src: Some(src.into()), span: Some(text_range_to_source_span(span)), message: msg, } .into() } pub fn eval_error(msg: impl Into) -> Box { Error::EvalError { src: None, span: None, message: msg.into(), stack_trace: Vec::new(), } .into() } pub fn internal(msg: String) -> Box { Error::InternalError { message: msg }.into() } pub fn catchable(msg: String) -> Box { Error::Catchable { message: msg }.into() } pub fn with_span(mut self: Box, span: rnix::TextRange) -> Box { 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, source: Source) -> Box { 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>, }