1use std::{collections::BTreeMap, fmt};
22
23use thiserror::Error;
24
25use crate::{RObject, RStr, RValue};
26
27#[derive(Debug, Error, PartialEq, Eq)]
29#[non_exhaustive]
30pub enum ViewError {
31 #[error("missing value at {path}")]
32 Missing { path: String, field: Option<String> },
33 #[error("unexpected type at {path}: expected {expected}, got {actual}")]
34 UnexpectedType {
35 path: String,
36 field: Option<String>,
37 expected: &'static str,
38 actual: &'static str,
39 },
40 #[error("unexpected length at {path}: expected {expected}, got {actual}")]
41 UnexpectedLength {
42 path: String,
43 field: Option<String>,
44 expected: String,
45 actual: usize,
46 },
47 #[error("duplicate name at {path}")]
48 DuplicateName { path: String, field: Option<String> },
49 #[error("invalid string encoding at {path}")]
50 InvalidStringEncoding {
51 path: String,
52 field: Option<String>,
53 row: Option<usize>,
54 column: Option<String>,
55 },
56 #[error("invalid dimensions at {path}: {reason}")]
57 InvalidDimensions {
58 path: String,
59 field: Option<String>,
60 reason: String,
61 },
62 #[error("invalid package version at {path}: {reason}")]
63 InvalidPackageVersion {
64 path: String,
65 field: Option<String>,
66 reason: String,
67 },
68}
69
70impl ViewError {
71 pub fn path(&self) -> String {
73 match self {
74 Self::Missing { path, .. }
75 | Self::UnexpectedType { path, .. }
76 | Self::UnexpectedLength { path, .. }
77 | Self::DuplicateName { path, .. }
78 | Self::InvalidStringEncoding { path, .. }
79 | Self::InvalidDimensions { path, .. }
80 | Self::InvalidPackageVersion { path, .. } => path.clone(),
81 }
82 }
83
84 pub fn field(&self) -> Option<&str> {
86 match self {
87 Self::Missing { field, .. }
88 | Self::UnexpectedType { field, .. }
89 | Self::UnexpectedLength { field, .. }
90 | Self::DuplicateName { field, .. }
91 | Self::InvalidStringEncoding { field, .. }
92 | Self::InvalidDimensions { field, .. }
93 | Self::InvalidPackageVersion { field, .. } => field.as_deref(),
94 }
95 }
96
97 pub fn row(&self) -> Option<usize> {
99 match self {
100 Self::InvalidStringEncoding { row, .. } => *row,
101 _ => None,
102 }
103 }
104
105 pub fn column(&self) -> Option<&str> {
107 match self {
108 Self::InvalidStringEncoding { column, .. } => column.as_deref(),
109 _ => None,
110 }
111 }
112}
113
114#[derive(Debug, Clone, PartialEq, Eq)]
116pub struct PackagesMatrix {
117 nrow: usize,
118 column_names: Vec<String>,
119 cells: Vec<Option<String>>,
120}
121
122impl PackagesMatrix {
123 pub fn from_object(object: &RObject) -> Result<Self, ViewError> {
125 let values = match &object.value() {
126 RValue::Character(values) => values,
127 value => {
128 return Err(unexpected_type(
129 "PACKAGES",
130 None,
131 "character vector",
132 value.kind_name(),
133 ));
134 }
135 };
136 let dimensions = object
137 .attributes()
138 .get("dim")
139 .ok_or_else(|| missing("PACKAGES.attributes.dim", None))?;
140 let dimensions = match &dimensions.value() {
141 RValue::Integer(values) => values,
142 value => {
143 return Err(unexpected_type(
144 "PACKAGES.attributes.dim",
145 None,
146 "integer vector",
147 value.kind_name(),
148 ));
149 }
150 };
151 if dimensions.len() != 2 {
152 return Err(unexpected_length(
153 "PACKAGES.attributes.dim",
154 None,
155 "2".to_owned(),
156 dimensions.len(),
157 ));
158 }
159 let mut shape = [0usize; 2];
160 for (index, value) in dimensions.iter().enumerate() {
161 let Some(value) = value else {
162 return Err(invalid_dimensions(
163 "PACKAGES.attributes.dim",
164 "dimensions must not contain NA",
165 ));
166 };
167 if *value < 0 {
168 return Err(invalid_dimensions(
169 "PACKAGES.attributes.dim",
170 "dimensions must not be negative",
171 ));
172 }
173 shape[index] = *value as usize;
174 }
175 let element_count = shape[0].checked_mul(shape[1]).ok_or_else(|| {
176 invalid_dimensions("PACKAGES.attributes.dim", "dimension product overflows")
177 })?;
178 if values.len() != element_count {
179 return Err(unexpected_length(
180 "PACKAGES",
181 None,
182 element_count.to_string(),
183 values.len(),
184 ));
185 }
186
187 let dimnames = object
188 .attributes()
189 .get("dimnames")
190 .ok_or_else(|| missing("PACKAGES.attributes.dimnames", None))?;
191 let dimnames = match &dimnames.value() {
192 RValue::List(values) => values,
193 value => {
194 return Err(unexpected_type(
195 "PACKAGES.attributes.dimnames",
196 None,
197 "list",
198 value.kind_name(),
199 ));
200 }
201 };
202 if dimnames.len() != 2 {
203 return Err(unexpected_length(
204 "PACKAGES.attributes.dimnames",
205 None,
206 "2".to_owned(),
207 dimnames.len(),
208 ));
209 }
210 validate_row_names(&dimnames[0], shape[0])?;
211 let column_values = match &dimnames[1].value() {
212 RValue::Character(values) => values,
213 value => {
214 return Err(unexpected_type(
215 "PACKAGES.attributes.dimnames[1]",
216 None,
217 "character vector",
218 value.kind_name(),
219 ));
220 }
221 };
222 if column_values.len() != shape[1] {
223 return Err(unexpected_length(
224 "PACKAGES.attributes.dimnames[1]",
225 None,
226 shape[1].to_string(),
227 column_values.len(),
228 ));
229 }
230 let mut column_names = Vec::with_capacity(shape[1]);
231 let mut seen_names = std::collections::BTreeSet::new();
232 for (index, value) in column_values.iter().enumerate() {
233 let name = decode_required(
234 value,
235 &format!("PACKAGES.attributes.dimnames[1][{index}]"),
236 None,
237 )?;
238 if !seen_names.insert(name.clone()) {
239 return Err(duplicate(
240 &format!("PACKAGES.attributes.dimnames[1][{index}]"),
241 Some(name),
242 ));
243 }
244 column_names.push(name);
245 }
246
247 let mut cells = Vec::with_capacity(element_count);
248 for row in 0..shape[0] {
249 for column in 0..shape[1] {
250 cells.push(decode_matrix_cell(
251 &values[row + column * shape[0]],
252 row,
253 &column_names[column],
254 )?);
255 }
256 }
257 Ok(Self {
258 nrow: shape[0],
259 column_names,
260 cells,
261 })
262 }
263
264 pub fn len(&self) -> usize {
265 self.nrow
266 }
267 pub fn is_empty(&self) -> bool {
268 self.nrow == 0
269 }
270 pub fn column_names(&self) -> impl ExactSizeIterator<Item = &str> + '_ {
271 self.column_names.iter().map(String::as_str)
272 }
273 pub fn column(&self, name: &str) -> Option<PackagesColumn<'_>> {
274 self.column_names
275 .iter()
276 .position(|column| column == name)
277 .map(|index| PackagesColumn {
278 matrix: self,
279 index,
280 })
281 }
282 pub fn row(&self, index: usize) -> Option<PackagesRow<'_>> {
283 (index < self.nrow).then_some(PackagesRow {
284 matrix: self,
285 index,
286 })
287 }
288 pub fn rows(&self) -> impl ExactSizeIterator<Item = PackagesRow<'_>> + '_ {
289 (0..self.nrow).map(|index| PackagesRow {
290 matrix: self,
291 index,
292 })
293 }
294}
295
296impl TryFrom<&RObject> for PackagesMatrix {
297 type Error = ViewError;
298 fn try_from(value: &RObject) -> Result<Self, Self::Error> {
299 Self::from_object(value)
300 }
301}
302
303#[derive(Debug, Clone, Copy)]
305pub struct PackagesRow<'a> {
306 matrix: &'a PackagesMatrix,
307 index: usize,
308}
309
310impl<'a> PackagesRow<'a> {
311 pub fn index(&self) -> usize {
312 self.index
313 }
314 pub fn get(&self, column: &str) -> Option<Option<&'a str>> {
315 let column = self
316 .matrix
317 .column_names
318 .iter()
319 .position(|name| name == column)?;
320 Some(self.matrix.cells[self.index * self.matrix.column_names.len() + column].as_deref())
321 }
322}
323
324#[derive(Debug, Clone, Copy)]
326pub struct PackagesColumn<'a> {
327 matrix: &'a PackagesMatrix,
328 index: usize,
329}
330
331impl<'a> PackagesColumn<'a> {
332 pub fn name(&self) -> &str {
333 &self.matrix.column_names[self.index]
334 }
335 pub fn len(&self) -> usize {
336 self.matrix.nrow
337 }
338 pub fn is_empty(&self) -> bool {
339 self.matrix.is_empty()
340 }
341 pub fn get(&self, row: usize) -> Option<Option<&'a str>> {
342 (row < self.matrix.nrow).then(|| {
343 self.matrix.cells[row * self.matrix.column_names.len() + self.index].as_deref()
344 })
345 }
346}
347
348#[derive(Debug, Clone, PartialEq, Eq)]
350pub struct PackageMeta {
351 description: BTreeMap<String, Option<String>>,
352 built: Option<Built>,
353}
354
355impl PackageMeta {
356 pub fn from_object(object: &RObject) -> Result<Self, ViewError> {
358 let items = expect_list(object, "PackageMeta", None)?;
359 require_class(object, "PackageMeta", None, "packageDescription2")?;
360 let names = named_values(object, "PackageMeta", None)?;
361 if names.len() != items.len() {
362 return Err(unexpected_length(
363 "PackageMeta",
364 None,
365 items.len().to_string(),
366 names.len(),
367 ));
368 }
369
370 let mut positions = BTreeMap::new();
371 for (index, name) in names.iter().enumerate() {
372 let name = decode_required(name, &format!("PackageMeta[{index}]"), None)?;
373 if positions.insert(name.clone(), index).is_some() {
374 return Err(duplicate(&format!("PackageMeta.{name}"), Some(name)));
375 }
376 }
377
378 let description_index = positions
379 .get("DESCRIPTION")
380 .copied()
381 .ok_or_else(|| missing("PackageMeta.DESCRIPTION", Some("DESCRIPTION".to_owned())))?;
382 let description = parse_description(
383 &items[description_index],
384 "PackageMeta.DESCRIPTION",
385 Some("DESCRIPTION"),
386 )?;
387 let built = positions
388 .get("Built")
389 .copied()
390 .map(|index| parse_built(&items[index], "PackageMeta.Built"))
391 .transpose()?;
392
393 Ok(Self { description, built })
394 }
395
396 pub fn built(&self) -> Option<&Built> {
398 self.built.as_ref()
399 }
400
401 pub fn description(&self) -> &BTreeMap<String, Option<String>> {
403 &self.description
404 }
405
406 pub fn description_field(&self, name: &str) -> Option<Option<&str>> {
408 self.description.get(name).map(|value| value.as_deref())
409 }
410}
411
412impl TryFrom<&RObject> for PackageMeta {
413 type Error = ViewError;
414
415 fn try_from(value: &RObject) -> Result<Self, Self::Error> {
416 Self::from_object(value)
417 }
418}
419
420#[derive(Debug, Clone, PartialEq, Eq)]
429pub struct Built {
430 r_version: PackageVersion,
431 platform: Option<String>,
432 date: Option<String>,
433 os_type: Option<String>,
434}
435
436impl Built {
437 pub fn r_version(&self) -> &PackageVersion {
439 &self.r_version
440 }
441
442 pub fn platform(&self) -> Option<&str> {
444 self.platform.as_deref()
445 }
446
447 pub fn date(&self) -> Option<&str> {
449 self.date.as_deref()
450 }
451
452 pub fn os_type(&self) -> Option<&str> {
454 self.os_type.as_deref()
455 }
456}
457
458#[derive(Debug, Clone, PartialEq, Eq)]
460pub struct PackageVersion {
461 components: Vec<u32>,
462}
463
464impl PackageVersion {
465 pub fn components(&self) -> &[u32] {
467 &self.components
468 }
469}
470
471impl fmt::Display for PackageVersion {
472 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
473 let mut components = self.components.iter();
474 if let Some(first) = components.next() {
475 write!(formatter, "{first}")?;
476 for component in components {
477 write!(formatter, ".{component}")?;
478 }
479 }
480 Ok(())
481 }
482}
483
484fn parse_description(
485 object: &RObject,
486 path: &str,
487 field: Option<&str>,
488) -> Result<BTreeMap<String, Option<String>>, ViewError> {
489 let values = match &object.value() {
490 RValue::Character(values) => values,
491 value => {
492 return Err(unexpected_type(
493 path,
494 field,
495 "character vector",
496 value.kind_name(),
497 ));
498 }
499 };
500 let names = named_values(object, path, field)?;
501 if names.len() != values.len() {
502 return Err(unexpected_length(
503 path,
504 field,
505 values.len().to_string(),
506 names.len(),
507 ));
508 }
509 let mut description = BTreeMap::new();
510 for (index, (name, value)) in names.iter().zip(values).enumerate() {
511 let name = decode_required(name, &format!("{path}[{index}]"), field)?;
512 if description.contains_key(&name) {
513 return Err(duplicate(&format!("{path}[\"{name}\"]"), Some(name)));
514 }
515 let value = decode_optional(value, &format!("{path}[\"{name}\"]"), Some(&name))?;
516 description.insert(name, value);
517 }
518 Ok(description)
519}
520
521fn parse_built(object: &RObject, path: &str) -> Result<Built, ViewError> {
522 let items = expect_list(object, path, Some("Built"))?;
523 let names = named_values(object, path, Some("Built"))?;
524 if names.len() != items.len() {
525 return Err(unexpected_length(
526 path,
527 Some("Built"),
528 items.len().to_string(),
529 names.len(),
530 ));
531 }
532 let mut positions = BTreeMap::new();
533 for (index, name) in names.iter().enumerate() {
534 let name = decode_required(name, &format!("{path}[{index}]"), Some("Built"))?;
535 if positions.insert(name.clone(), index).is_some() {
536 return Err(duplicate(&format!("{path}.{name}"), Some(name)));
537 }
538 }
539 let r_index = positions
540 .get("R")
541 .copied()
542 .ok_or_else(|| missing(format!("{path}.R"), Some("R".to_owned())))?;
543 let r_version = parse_version(&items[r_index], &format!("{path}.R"))?;
544 let platform = optional_built_string(&positions, items, "Platform", path)?;
545 let date = optional_built_string(&positions, items, "Date", path)?;
546 let os_type = optional_built_string(&positions, items, "OStype", path)?;
547 Ok(Built {
548 r_version,
549 platform,
550 date,
551 os_type,
552 })
553}
554
555fn parse_version(object: &RObject, path: &str) -> Result<PackageVersion, ViewError> {
556 let valid_class = match object.attributes().get("class") {
557 Some(attribute) => match &attribute.value() {
558 RValue::Character(values) => {
559 let mut has_package = false;
560 let mut has_numeric = false;
561 for value in values {
562 match value.as_str() {
563 Some(Ok(value)) if value == "package_version" => has_package = true,
564 Some(Ok(value)) if value == "numeric_version" => has_numeric = true,
565 Some(Err(_)) => {
566 return Err(ViewError::InvalidStringEncoding {
567 path: format!("{path}.class"),
568 field: Some("R".to_owned()),
569 row: None,
570 column: None,
571 });
572 }
573 _ => {}
574 }
575 }
576 has_package && has_numeric
577 }
578 _ => false,
579 },
580 None => false,
581 };
582 if !valid_class {
583 return Err(invalid_version(
584 path,
585 "missing package_version/numeric_version class",
586 ));
587 }
588 let RValue::List(values) = &object.value() else {
589 return Err(invalid_version(path, "expected a length-one list"));
590 };
591 if values.len() != 1 {
592 return Err(invalid_version(path, "expected a length-one list"));
593 }
594 let RValue::Integer(components) = &values[0].value() else {
595 return Err(invalid_version(path, "expected an integer vector"));
596 };
597 if components.is_empty() {
598 return Err(invalid_version(
599 path,
600 "version components must not be empty",
601 ));
602 }
603 let mut owned = Vec::with_capacity(components.len());
604 for (index, component) in components.iter().enumerate() {
605 let Some(component) = component else {
606 return Err(invalid_version(
607 &format!("{path}[0][{index}]"),
608 "component is NA",
609 ));
610 };
611 if *component < 0 {
612 return Err(invalid_version(
613 &format!("{path}[0][{index}]"),
614 "component is negative",
615 ));
616 }
617 owned.push(*component as u32);
618 }
619 Ok(PackageVersion { components: owned })
620}
621
622fn optional_built_string(
623 positions: &BTreeMap<String, usize>,
624 items: &[RObject],
625 name: &str,
626 path: &str,
627) -> Result<Option<String>, ViewError> {
628 match positions.get(name) {
629 Some(index) => {
630 decode_character_scalar(&items[*index], &format!("{path}.{name}"), Some(name))
631 }
632 None => Ok(None),
633 }
634}
635
636fn expect_list<'a>(
637 object: &'a RObject,
638 path: &str,
639 field: Option<&str>,
640) -> Result<&'a [RObject], ViewError> {
641 match &object.value() {
642 RValue::List(values) => Ok(values),
643 value => Err(unexpected_type(path, field, "list", value.kind_name())),
644 }
645}
646
647fn named_values<'a>(
648 object: &'a RObject,
649 path: &str,
650 field: Option<&str>,
651) -> Result<&'a [RStr], ViewError> {
652 let Some(attribute) = object.attributes().get("names") else {
653 return Err(missing(format!("{path}.names"), field.map(str::to_owned)));
654 };
655 match &attribute.value() {
656 RValue::Character(values) => Ok(values),
657 value => Err(unexpected_type(
658 &format!("{path}.names"),
659 field,
660 "character vector",
661 value.kind_name(),
662 )),
663 }
664}
665
666fn require_class(
667 object: &RObject,
668 path: &str,
669 field: Option<&str>,
670 expected: &str,
671) -> Result<(), ViewError> {
672 let Some(attribute) = object.attributes().get("class") else {
673 return Err(missing(format!("{path}.class"), field.map(str::to_owned)));
674 };
675 let RValue::Character(values) = &attribute.value() else {
676 return Err(unexpected_type(
677 &format!("{path}.class"),
678 field,
679 "character vector",
680 attribute.value().kind_name(),
681 ));
682 };
683 for value in values {
684 match value.as_str() {
685 Some(Ok(value)) if value == expected => return Ok(()),
686 Some(Err(_)) => {
687 return Err(ViewError::InvalidStringEncoding {
688 path: format!("{path}.class"),
689 field: field.map(str::to_owned),
690 row: None,
691 column: None,
692 });
693 }
694 _ => {}
695 }
696 }
697 Err(unexpected_type(
698 path,
699 field,
700 "expected class",
701 "different class",
702 ))
703}
704
705fn decode_required(value: &RStr, path: &str, field: Option<&str>) -> Result<String, ViewError> {
706 match value.as_str() {
707 None => Err(unexpected_type(path, field, "non-NA string", "NA")),
708 Some(Ok(value)) => Ok(value.into_owned()),
709 Some(Err(_)) => Err(ViewError::InvalidStringEncoding {
710 path: path.to_owned(),
711 field: field.map(str::to_owned),
712 row: None,
713 column: None,
714 }),
715 }
716}
717
718fn decode_optional(
719 value: &RStr,
720 path: &str,
721 field: Option<&str>,
722) -> Result<Option<String>, ViewError> {
723 match value.as_str() {
724 None => Ok(None),
725 Some(Ok(value)) => Ok(Some(value.into_owned())),
726 Some(Err(_)) => Err(ViewError::InvalidStringEncoding {
727 path: path.to_owned(),
728 field: field.map(str::to_owned),
729 row: None,
730 column: None,
731 }),
732 }
733}
734
735fn decode_character_scalar(
736 object: &RObject,
737 path: &str,
738 field: Option<&str>,
739) -> Result<Option<String>, ViewError> {
740 let RValue::Character(values) = &object.value() else {
741 return Err(unexpected_type(
742 path,
743 field,
744 "character scalar",
745 object.value().kind_name(),
746 ));
747 };
748 if values.len() != 1 {
749 return Err(unexpected_length(path, field, "1".to_owned(), values.len()));
750 }
751 decode_optional(&values[0], path, field)
752}
753
754fn validate_row_names(object: &RObject, expected: usize) -> Result<(), ViewError> {
755 let values = match &object.value() {
756 RValue::Null => return Ok(()),
757 RValue::Character(values) => values,
758 value => {
759 return Err(unexpected_type(
760 "PACKAGES.attributes.dimnames[0]",
761 None,
762 "character vector",
763 value.kind_name(),
764 ));
765 }
766 };
767 if values.len() != expected {
768 return Err(unexpected_length(
769 "PACKAGES.attributes.dimnames[0]",
770 None,
771 expected.to_string(),
772 values.len(),
773 ));
774 }
775 for (index, value) in values.iter().enumerate() {
776 if let Some(Err(_)) = value.as_str() {
777 return Err(ViewError::InvalidStringEncoding {
778 path: format!("PACKAGES.attributes.dimnames[0][{index}]"),
779 field: None,
780 row: None,
781 column: None,
782 });
783 }
784 }
785 Ok(())
786}
787
788fn decode_matrix_cell(value: &RStr, row: usize, column: &str) -> Result<Option<String>, ViewError> {
789 match value.as_str() {
790 None => Ok(None),
791 Some(Ok(value)) => Ok(Some(value.into_owned())),
792 Some(Err(_)) => Err(ViewError::InvalidStringEncoding {
793 path: format!("PACKAGES[row={row},column=\"{column}\"]"),
794 field: Some(column.to_owned()),
795 row: Some(row),
796 column: Some(column.to_owned()),
797 }),
798 }
799}
800
801fn missing(path: impl Into<String>, field: Option<String>) -> ViewError {
802 ViewError::Missing {
803 path: path.into(),
804 field,
805 }
806}
807
808fn invalid_dimensions(path: &str, reason: &str) -> ViewError {
809 ViewError::InvalidDimensions {
810 path: path.to_owned(),
811 field: None,
812 reason: reason.to_owned(),
813 }
814}
815
816fn unexpected_type(
817 path: &str,
818 field: Option<&str>,
819 expected: &'static str,
820 actual: &'static str,
821) -> ViewError {
822 ViewError::UnexpectedType {
823 path: path.to_owned(),
824 field: field.map(str::to_owned),
825 expected,
826 actual,
827 }
828}
829
830fn unexpected_length(
831 path: &str,
832 field: Option<&str>,
833 expected: String,
834 actual: usize,
835) -> ViewError {
836 ViewError::UnexpectedLength {
837 path: path.to_owned(),
838 field: field.map(str::to_owned),
839 expected,
840 actual,
841 }
842}
843
844fn duplicate(path: &str, field: Option<String>) -> ViewError {
845 ViewError::DuplicateName {
846 path: path.to_owned(),
847 field,
848 }
849}
850
851fn invalid_version(path: &str, reason: &str) -> ViewError {
852 ViewError::InvalidPackageVersion {
853 path: path.to_owned(),
854 field: Some("R".to_owned()),
855 reason: reason.to_owned(),
856 }
857}
858
859trait ValueKindName {
860 fn kind_name(&self) -> &'static str;
861}
862
863impl ValueKindName for RValue {
864 fn kind_name(&self) -> &'static str {
865 match self {
866 Self::Null => "null",
867 Self::Logical(_) => "logical vector",
868 Self::Integer(_) => "integer vector",
869 Self::Real(_) => "real vector",
870 Self::Character(_) => "character vector",
871 Self::List(_) => "list",
872 Self::Symbol(_) => "symbol",
873 Self::Persisted(_) => "persisted value",
874 Self::Environment(_) => "environment",
875 }
876 }
877}
878
879#[cfg(test)]
880mod tests {
881 use super::*;
882 use crate::{Attribute, Attributes, REncoding, Symbol};
883
884 #[test]
885 fn package_version_displays_components() {
886 let version = PackageVersion {
887 components: vec![4, 6, 1],
888 };
889 assert_eq!(version.to_string(), "4.6.1");
890 assert_eq!(version.components(), &[4, 6, 1]);
891 }
892
893 #[test]
894 fn view_error_exposes_logical_context() {
895 let error = ViewError::DuplicateName {
896 path: "PackageMeta.DESCRIPTION[\"Package\"]".to_owned(),
897 field: Some("Package".to_owned()),
898 };
899 assert_eq!(error.path(), "PackageMeta.DESCRIPTION[\"Package\"]");
900 assert_eq!(error.field(), Some("Package"));
901 assert_eq!(error.row(), None);
902 assert_eq!(error.column(), None);
903 }
904
905 fn matrix(dim: Vec<Option<i32>>, dimnames: Vec<RObject>, values: Vec<RStr>) -> RObject {
906 RObject::from_parts(
907 RValue::Character(values),
908 Attributes::new(vec![
909 Attribute::new(
910 Symbol::new("dim"),
911 RObject::from_parts(RValue::Integer(dim), Attributes::default()),
912 ),
913 Attribute::new(
914 Symbol::new("dimnames"),
915 RObject::from_parts(RValue::List(dimnames), Attributes::default()),
916 ),
917 ]),
918 )
919 }
920
921 fn names(values: &[&str]) -> RObject {
922 RObject::from_parts(
923 RValue::Character(
924 values
925 .iter()
926 .map(|value| RStr::new(value.as_bytes(), REncoding::Native, None))
927 .collect(),
928 ),
929 Attributes::default(),
930 )
931 }
932
933 fn replace_dimnames(object: &mut RObject, replacement: Vec<RObject>) {
934 let dim = object.attributes().get("dim").unwrap().clone();
935 set_attributes(
936 object,
937 Attributes::new(vec![
938 Attribute::new(Symbol::new("dim"), dim),
939 Attribute::new(
940 Symbol::new("dimnames"),
941 RObject::from_parts(RValue::List(replacement), Attributes::default()),
942 ),
943 ]),
944 );
945 }
946
947 fn set_attributes(object: &mut RObject, attributes: Attributes) {
948 let (value, _) = object.clone().into_parts();
949 *object = RObject::from_parts(value, attributes);
950 }
951
952 fn set_value(object: &mut RObject, value: RValue) {
953 let (_, attributes) = object.clone().into_parts();
954 *object = RObject::from_parts(value, attributes);
955 }
956
957 #[test]
958 fn packages_matrix_rejects_malformed_shape_and_names() {
959 let valid = || {
960 matrix(
961 vec![Some(1), Some(1)],
962 vec![names(&["row"]), names(&["Package"])],
963 vec![RStr::new(b"x", REncoding::Native, None)],
964 )
965 };
966 assert!(
967 matches!(PackagesMatrix::from_object(&RObject::from_parts(RValue::Character(vec![]), Attributes::default())), Err(ViewError::Missing { path, .. }) if path == "PACKAGES.attributes.dim")
968 );
969 let mut object = valid();
970 set_attributes(
971 &mut object,
972 Attributes::new(vec![Attribute::new(
973 Symbol::new("dim"),
974 RObject::from_parts(RValue::Character(vec![]), Attributes::default()),
975 )]),
976 );
977 assert!(
978 matches!(PackagesMatrix::from_object(&object), Err(ViewError::UnexpectedType { path, .. }) if path == "PACKAGES.attributes.dim")
979 );
980 let mut object = valid();
981 set_attributes(
982 &mut object,
983 Attributes::new(vec![Attribute::new(
984 Symbol::new("dim"),
985 RObject::from_parts(RValue::Integer(vec![Some(1)]), Attributes::default()),
986 )]),
987 );
988 assert!(
989 matches!(PackagesMatrix::from_object(&object), Err(ViewError::UnexpectedLength { path, .. }) if path == "PACKAGES.attributes.dim")
990 );
991 let mut object = valid();
992 set_attributes(
993 &mut object,
994 Attributes::new(vec![
995 Attribute::new(
996 Symbol::new("dim"),
997 RObject::from_parts(
998 RValue::Integer(vec![None, Some(1)]),
999 Attributes::default(),
1000 ),
1001 ),
1002 Attribute::new(
1003 Symbol::new("dimnames"),
1004 RObject::from_parts(
1005 RValue::List(vec![names(&["row"]), names(&["Package"])]),
1006 Attributes::default(),
1007 ),
1008 ),
1009 ]),
1010 );
1011 assert!(
1012 matches!(PackagesMatrix::from_object(&object), Err(ViewError::InvalidDimensions { path, .. }) if path == "PACKAGES.attributes.dim")
1013 );
1014 let mut object = valid();
1015 set_attributes(
1016 &mut object,
1017 Attributes::new(vec![
1018 Attribute::new(
1019 Symbol::new("dim"),
1020 RObject::from_parts(
1021 RValue::Integer(vec![Some(-1), Some(1)]),
1022 Attributes::default(),
1023 ),
1024 ),
1025 Attribute::new(
1026 Symbol::new("dimnames"),
1027 RObject::from_parts(
1028 RValue::List(vec![names(&[]), names(&["Package"])]),
1029 Attributes::default(),
1030 ),
1031 ),
1032 ]),
1033 );
1034 assert!(
1035 matches!(PackagesMatrix::from_object(&object), Err(ViewError::InvalidDimensions { path, .. }) if path == "PACKAGES.attributes.dim")
1036 );
1037 let mut object = valid();
1038 replace_dimnames(
1039 &mut object,
1040 vec![names(&["row"]), names(&["Package", "Version"])],
1041 );
1042 assert!(
1043 matches!(PackagesMatrix::from_object(&object), Err(ViewError::UnexpectedLength { path, .. }) if path == "PACKAGES.attributes.dimnames[1]")
1044 );
1045 let mut object = valid();
1046 replace_dimnames(&mut object, vec![names(&["row"]), names(&["Package"])]);
1047 set_value(&mut object, RValue::Character(vec![]));
1048 assert!(
1049 matches!(PackagesMatrix::from_object(&object), Err(ViewError::UnexpectedLength { path, .. }) if path == "PACKAGES")
1050 );
1051 let object = RObject::from_parts(
1052 RValue::Character(vec![RStr::new(b"x", REncoding::Native, None)]),
1053 Attributes::new(vec![Attribute::new(
1054 Symbol::new("dim"),
1055 RObject::from_parts(
1056 RValue::Integer(vec![Some(1), Some(1)]),
1057 Attributes::default(),
1058 ),
1059 )]),
1060 );
1061 assert!(
1062 matches!(PackagesMatrix::from_object(&object), Err(ViewError::Missing { path, .. }) if path == "PACKAGES.attributes.dimnames")
1063 );
1064 let mut object = valid();
1065 replace_dimnames(&mut object, vec![names(&["row"])]);
1066 assert!(
1067 matches!(PackagesMatrix::from_object(&object), Err(ViewError::UnexpectedLength { path, .. }) if path == "PACKAGES.attributes.dimnames")
1068 );
1069 let object = matrix(
1070 vec![Some(i32::MAX), Some(i32::MAX)],
1071 vec![
1072 RObject::from_parts(RValue::Null, Attributes::default()),
1073 RObject::from_parts(RValue::Character(vec![]), Attributes::default()),
1074 ],
1075 vec![],
1076 );
1077 let error = PackagesMatrix::from_object(&object).unwrap_err();
1080 if usize::BITS >= 64 {
1081 assert!(
1082 matches!(error, ViewError::UnexpectedLength { ref path, .. } if path == "PACKAGES")
1083 );
1084 } else {
1085 assert!(matches!(error, ViewError::InvalidDimensions { .. }));
1086 }
1087 }
1088
1089 #[test]
1090 fn packages_matrix_rejects_na_and_duplicate_column_names() {
1091 let mut object = matrix(
1092 vec![Some(1), Some(2)],
1093 vec![names(&["row"]), names(&["Package", "Package"])],
1094 vec![
1095 RStr::new(b"x", REncoding::Native, None),
1096 RStr::new(b"y", REncoding::Native, None),
1097 ],
1098 );
1099 assert!(
1100 matches!(PackagesMatrix::from_object(&object), Err(ViewError::DuplicateName { path, .. }) if path == "PACKAGES.attributes.dimnames[1][1]")
1101 );
1102 replace_dimnames(
1103 &mut object,
1104 vec![
1105 names(&["row"]),
1106 RObject::from_parts(
1107 RValue::Character(vec![
1108 RStr::Na,
1109 RStr::new(b"Version", REncoding::Native, None),
1110 ]),
1111 Attributes::default(),
1112 ),
1113 ],
1114 );
1115 assert!(
1116 matches!(PackagesMatrix::from_object(&object), Err(ViewError::UnexpectedType { path, .. }) if path == "PACKAGES.attributes.dimnames[1][0]")
1117 );
1118 }
1119}