use thiserror::Error;
use crate::{RObject, RStr, RValue};
mod namespace;
#[cfg(feature = "lazyload")]
mod installed_code;
#[cfg(feature = "lazyload")]
pub use installed_code::{
BodyValidation, CodeDbGeneration, CodeDbProvenance, DefaultPresence, FailureCause,
FailurePhase, Formal, FormalsInspection, FormalsNotApplicable, FormalsUnavailable,
FunctionFormals, InspectionExtent, InstalledCodeDb, InstalledCodeError, InstalledCodeOptions,
PrefixFailure, StoredBinding, StoredKind, StoredObjectInspection,
};
pub use namespace::{
ImportedName, MetadataField, NamespaceExport, NamespaceImport, NamespaceMetadata, S3MethodName,
S3Registration,
};
#[derive(Debug, Error, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum ViewError {
#[error("missing value at {path}")]
Missing { path: String, field: Option<String> },
#[error("unexpected type at {path}: expected {expected}, got {actual}")]
UnexpectedType {
path: String,
field: Option<String>,
expected: &'static str,
actual: &'static str,
},
#[error("unexpected length at {path}: expected {expected}, got {actual}")]
UnexpectedLength {
path: String,
field: Option<String>,
expected: String,
actual: usize,
},
#[error("duplicate name at {path}")]
DuplicateName { path: String, field: Option<String> },
#[error("invalid string encoding at {path}")]
InvalidStringEncoding {
path: String,
field: Option<String>,
row: Option<usize>,
column: Option<String>,
},
#[error("invalid dimensions at {path}: {reason}")]
InvalidDimensions {
path: String,
field: Option<String>,
reason: String,
},
#[error("invalid package version at {path}: {reason}")]
InvalidPackageVersion {
path: String,
field: Option<String>,
reason: String,
},
}
impl ViewError {
pub fn path(&self) -> String {
match self {
Self::Missing { path, .. }
| Self::UnexpectedType { path, .. }
| Self::UnexpectedLength { path, .. }
| Self::DuplicateName { path, .. }
| Self::InvalidStringEncoding { path, .. }
| Self::InvalidDimensions { path, .. }
| Self::InvalidPackageVersion { path, .. } => path.clone(),
}
}
pub fn field(&self) -> Option<&str> {
match self {
Self::Missing { field, .. }
| Self::UnexpectedType { field, .. }
| Self::UnexpectedLength { field, .. }
| Self::DuplicateName { field, .. }
| Self::InvalidStringEncoding { field, .. }
| Self::InvalidDimensions { field, .. }
| Self::InvalidPackageVersion { field, .. } => field.as_deref(),
}
}
pub fn row(&self) -> Option<usize> {
match self {
Self::InvalidStringEncoding { row, .. } => *row,
_ => None,
}
}
pub fn column(&self) -> Option<&str> {
match self {
Self::InvalidStringEncoding { column, .. } => column.as_deref(),
_ => None,
}
}
}
mod meta;
mod packages;
pub use meta::{Built, PackageMeta, PackageVersion};
pub use packages::{PackagesColumn, PackagesMatrix, PackagesRow};
fn expect_list<'a>(
object: &'a RObject,
path: &str,
field: Option<&str>,
) -> Result<&'a [RObject], ViewError> {
match &object.value() {
RValue::List(values) => Ok(values),
value => Err(unexpected_type(path, field, "list", value.kind_name())),
}
}
fn named_values<'a>(
object: &'a RObject,
path: &str,
field: Option<&str>,
) -> Result<&'a [RStr], ViewError> {
let Some(attribute) = object.attributes().get("names") else {
return Err(missing(format!("{path}.names"), field.map(str::to_owned)));
};
match &attribute.value() {
RValue::Character(values) => Ok(values),
value => Err(unexpected_type(
&format!("{path}.names"),
field,
"character vector",
value.kind_name(),
)),
}
}
fn decode_optional(
value: &RStr,
path: &str,
field: Option<&str>,
) -> Result<Option<String>, ViewError> {
match value.as_str() {
None => Ok(None),
Some(Ok(value)) => Ok(Some(value.into_owned())),
Some(Err(_)) => Err(ViewError::InvalidStringEncoding {
path: path.to_owned(),
field: field.map(str::to_owned),
row: None,
column: None,
}),
}
}
fn invalid_dimensions(path: &str, reason: &str) -> ViewError {
ViewError::InvalidDimensions {
path: path.to_owned(),
field: None,
reason: reason.to_owned(),
}
}
fn decode_required(value: &RStr, path: &str, field: Option<&str>) -> Result<String, ViewError> {
match value.as_str() {
None => Err(unexpected_type(path, field, "non-NA string", "NA")),
Some(Ok(value)) => Ok(value.into_owned()),
Some(Err(_)) => Err(ViewError::InvalidStringEncoding {
path: path.to_owned(),
field: field.map(str::to_owned),
row: None,
column: None,
}),
}
}
fn missing(path: impl Into<String>, field: Option<String>) -> ViewError {
ViewError::Missing {
path: path.into(),
field,
}
}
fn unexpected_type(
path: &str,
field: Option<&str>,
expected: &'static str,
actual: &'static str,
) -> ViewError {
ViewError::UnexpectedType {
path: path.to_owned(),
field: field.map(str::to_owned),
expected,
actual,
}
}
fn unexpected_length(
path: &str,
field: Option<&str>,
expected: String,
actual: usize,
) -> ViewError {
ViewError::UnexpectedLength {
path: path.to_owned(),
field: field.map(str::to_owned),
expected,
actual,
}
}
fn duplicate(path: &str, field: Option<String>) -> ViewError {
ViewError::DuplicateName {
path: path.to_owned(),
field,
}
}
trait ValueKindName {
fn kind_name(&self) -> &'static str;
}
impl ValueKindName for RValue {
fn kind_name(&self) -> &'static str {
match self {
Self::Null => "null",
Self::Logical(_) => "logical vector",
Self::Integer(_) => "integer vector",
Self::Real(_) => "real vector",
Self::Character(_) => "character vector",
Self::List(_) => "list",
Self::Symbol(_) => "symbol",
Self::Persisted(_) => "persisted value",
Self::Environment(_) => "environment",
}
}
}
#[cfg(test)]
mod tests;