use magnus::{exception, Ruby}; use std::any; use std::fmt::Display; use std::io; use std::path::Path; use thiserror::Error; use crate::util; use crate::PROM_EPARSING_ERROR; /// A lightweight representation of Ruby ExceptionClasses. #[derive(PartialEq, Clone, Copy, Debug)] pub enum RubyError { Arg, Encoding, Frozen, Index, Io, NoMem, PromParsing, Runtime, Type, } impl From for magnus::ExceptionClass { fn from(err: RubyError) -> magnus::ExceptionClass { match err { RubyError::Arg => exception::arg_error(), RubyError::Encoding => exception::encoding_error(), RubyError::Frozen => exception::frozen_error(), RubyError::Index => exception::index_error(), RubyError::Io => exception::io_error(), RubyError::NoMem => exception::no_mem_error(), RubyError::Runtime => exception::runtime_error(), RubyError::PromParsing => { // UNWRAP: this will panic if called outside of a Ruby thread. let ruby = Ruby::get().unwrap(); ruby.get_inner(&PROM_EPARSING_ERROR) } RubyError::Type => exception::type_error(), } } } /// Errors returned internally within the crate. Methods called directly by Ruby return /// `magnus::error::Error` as do functions that interact heavily with Ruby. This can be /// converted into a `magnus::error::Error` at the boundary between Rust and Ruby. #[derive(PartialEq, Error, Debug)] pub enum MmapError { /// A read or write was made while another thread had mutable access to the mmap. #[error("read/write operation attempted while mmap was being written to")] ConcurrentAccess, /// An error message used to exactly match the messages returned by the C /// implementation. #[error("{0}")] Legacy(String, RubyError), /// A String had invalid UTF-8 sequences. #[error("{0}")] Encoding(String), /// A failed attempt to cast an integer from one type to another. #[error("failed to cast {object_name} {value} from {from} to {to}")] FailedCast { from: &'static str, to: &'static str, value: String, object_name: String, }, /// The mmap was frozen when a mutable operation was attempted. #[error("mmap")] Frozen, /// An io operation failed. #[error("failed to {operation} path '{path}': {err}")] Io { operation: String, path: String, err: String, }, #[error("string length gt {}", i32::MAX)] KeyLength, /// Failed to allocate memory. #[error("Couldn't allocate for {0} memory")] OutOfMemory(usize), /// A memory operation fell outside of the containers bounds. #[error("offset {index} out of bounds of len {len}")] OutOfBounds { index: String, len: String }, /// A numeric operation overflowed. #[error("overflow when {op} {value} and {added} of type {ty}")] Overflow { value: String, added: String, op: String, ty: &'static str, }, /// A miscellaneous error. #[error("{0}")] Other(String), /// A failure when parsing a `.db` file containing Prometheus metrics. #[error("{0}")] PromParsing(String), /// No mmap open. #[error("unmapped file")] UnmappedFile, /// A custom error message with `strerror(3)` appended. #[error("{0}")] WithErrno(String), } impl MmapError { pub fn legacy>(msg: T, ruby_err: RubyError) -> Self { MmapError::Legacy(msg.into(), ruby_err) } pub fn failed_cast(value: T, object_name: &str) -> Self { MmapError::FailedCast { from: any::type_name::(), to: any::type_name::(), value: value.to_string(), object_name: object_name.to_string(), } } pub fn io(operation: &str, path: &Path, err: io::Error) -> Self { MmapError::Io { operation: operation.to_string(), path: path.display().to_string(), err: err.to_string(), } } pub fn overflowed(value: T, added: T, op: &str) -> Self { MmapError::Overflow { value: value.to_string(), added: added.to_string(), op: op.to_string(), ty: any::type_name::(), } } pub fn out_of_bounds(index: T, len: T) -> Self { MmapError::OutOfBounds { index: index.to_string(), len: len.to_string(), } } pub fn with_errno>(msg: T) -> Self { let strerror = util::strerror(util::errno()); MmapError::WithErrno(format!("{}: ({strerror})", msg.into())) } pub fn ruby_err(&self) -> RubyError { match self { MmapError::ConcurrentAccess => RubyError::Arg, MmapError::Legacy(_, e) => *e, MmapError::Encoding(_) => RubyError::Encoding, MmapError::Io { .. } => RubyError::Io, MmapError::FailedCast { .. } => RubyError::Arg, MmapError::Frozen => RubyError::Frozen, MmapError::KeyLength => RubyError::Arg, MmapError::Overflow { .. } => RubyError::Arg, MmapError::OutOfBounds { .. } => RubyError::Index, MmapError::OutOfMemory { .. } => RubyError::NoMem, MmapError::Other(_) => RubyError::Arg, MmapError::PromParsing(_) => RubyError::PromParsing, MmapError::UnmappedFile => RubyError::Io, MmapError::WithErrno(_) => RubyError::Io, } } } impl From for magnus::error::Error { fn from(err: MmapError) -> magnus::error::Error { magnus::error::Error::new(err.ruby_err().into(), err.to_string()) } }