1use std::fmt;
10
11use super::{
12 InstalledMetadataError, ValueKindName, ViewError, decode_optional, decode_required,
13 expect_list, invalid_dimensions, named_values, unexpected_length, unexpected_type,
14};
15use crate::{RObject, RStr, RValue};
16
17#[derive(Debug, Clone, PartialEq, Eq)]
24#[non_exhaustive]
25pub enum MetadataField<T> {
26 Missing,
27 Present(T),
28 Invalid(ViewError),
29 UnsupportedSchema { description: String },
30}
31
32impl<T> MetadataField<T> {
33 pub fn as_ref(&self) -> MetadataField<&T> {
35 match self {
36 Self::Missing => MetadataField::Missing,
37 Self::Present(value) => MetadataField::Present(value),
38 Self::Invalid(error) => MetadataField::Invalid(error.clone()),
39 Self::UnsupportedSchema { description } => MetadataField::UnsupportedSchema {
40 description: description.clone(),
41 },
42 }
43 }
44
45 pub fn present(&self) -> Option<&T> {
47 match self {
48 Self::Present(value) => Some(value),
49 Self::Missing | Self::Invalid(_) | Self::UnsupportedSchema { .. } => None,
50 }
51 }
52}
53
54#[derive(Debug, Clone, PartialEq, Eq)]
56pub struct ImportedName {
57 source_name: String,
58 local_name: String,
59}
60
61#[derive(Debug, Clone, PartialEq, Eq)]
68pub struct NamespaceExport {
69 source_name: String,
70 exported_name: String,
71}
72
73impl NamespaceExport {
74 pub fn new(source_name: impl Into<String>, exported_name: impl Into<String>) -> Self {
76 Self {
77 source_name: source_name.into(),
78 exported_name: exported_name.into(),
79 }
80 }
81
82 pub fn source_name(&self) -> &str {
84 &self.source_name
85 }
86
87 pub fn exported_name(&self) -> &str {
89 &self.exported_name
90 }
91}
92
93impl ImportedName {
94 pub fn new(source_name: impl Into<String>, local_name: impl Into<String>) -> Self {
96 Self {
97 source_name: source_name.into(),
98 local_name: local_name.into(),
99 }
100 }
101
102 pub fn source_name(&self) -> &str {
104 &self.source_name
105 }
106
107 pub fn local_name(&self) -> &str {
109 &self.local_name
110 }
111}
112
113#[derive(Debug, Clone, PartialEq, Eq)]
115pub enum NamespaceImport {
116 All {
118 package: String,
119 except: Vec<String>,
120 },
121 From {
123 package: String,
124 names: Vec<ImportedName>,
125 },
126}
127
128impl NamespaceImport {
129 pub fn package(&self) -> &str {
131 match self {
132 Self::All { package, .. } | Self::From { package, .. } => package,
133 }
134 }
135}
136
137#[derive(Debug, Clone, PartialEq, Eq)]
139pub enum S3MethodName {
140 Implicit,
142 Explicit(String),
144}
145
146#[derive(Debug, Clone, PartialEq, Eq)]
148pub struct S3Registration {
149 generic: String,
150 class: String,
151 method: S3MethodName,
152 generic_package: Option<String>,
153}
154
155impl S3Registration {
156 pub fn generic(&self) -> &str {
157 &self.generic
158 }
159
160 pub fn class(&self) -> &str {
161 &self.class
162 }
163
164 pub fn method(&self) -> &S3MethodName {
165 &self.method
166 }
167
168 pub fn generic_package(&self) -> Option<&str> {
169 self.generic_package.as_deref()
170 }
171}
172
173#[derive(Debug, Clone, PartialEq, Eq)]
181pub struct NamespaceMetadata {
182 declared_exports: MetadataField<Vec<NamespaceExport>>,
183 export_patterns: MetadataField<Vec<String>>,
184 imports: MetadataField<Vec<NamespaceImport>>,
185 s3_registrations: MetadataField<Vec<S3Registration>>,
186 s3_generic_evidence: MetadataField<Vec<String>>,
187 export_classes: MetadataField<Vec<String>>,
188 export_methods: MetadataField<Vec<String>>,
189 export_class_patterns: MetadataField<Vec<String>>,
190}
191
192impl NamespaceMetadata {
193 pub fn read_installed(
196 package_dir: impl AsRef<std::path::Path>,
197 ) -> Result<Self, InstalledMetadataError> {
198 Self::read_installed_with_options(package_dir, &crate::file::ReadOptions::default())
199 }
200
201 pub fn read_installed_with_options(
204 package_dir: impl AsRef<std::path::Path>,
205 options: &crate::file::ReadOptions,
206 ) -> Result<Self, InstalledMetadataError> {
207 let (path, object) = super::read_installed_object(package_dir, "nsInfo.rds", options)?;
208 Self::from_object(&object).map_err(|source| InstalledMetadataError::View { path, source })
209 }
210
211 pub fn from_object(object: &RObject) -> Result<Self, ViewError> {
217 let items = expect_list(object, "NamespaceMetadata", None)?;
218 let names = named_values(object, "NamespaceMetadata", None)?;
219 if names.len() != items.len() {
220 return Err(unexpected_length(
221 "NamespaceMetadata",
222 None,
223 items.len().to_string(),
224 names.len(),
225 ));
226 }
227
228 let mut fields = Vec::with_capacity(items.len());
229 for (index, name) in names.iter().enumerate() {
230 let name = decode_required(name, &format!("NamespaceMetadata[{index}]"), None)?;
231 fields.push((name, &items[index]));
232 }
233
234 Ok(Self {
235 declared_exports: parse_known_field(&fields, "exports", parse_exports),
236 export_patterns: parse_known_field(&fields, "exportPatterns", parse_strings),
237 imports: parse_known_field(&fields, "imports", parse_imports),
238 s3_registrations: parse_known_field(&fields, "S3methods", parse_s3_registrations),
239 s3_generic_evidence: parse_known_field(&fields, "S3methods", parse_s3_generic_evidence),
240 export_classes: parse_known_field(&fields, "exportClasses", parse_strings),
241 export_methods: parse_known_field(&fields, "exportMethods", parse_strings),
242 export_class_patterns: parse_known_field(&fields, "exportClassPatterns", parse_strings),
243 })
244 }
245
246 pub fn declared_exports(&self) -> &MetadataField<Vec<NamespaceExport>> {
247 &self.declared_exports
248 }
249
250 pub fn export_patterns(&self) -> &MetadataField<Vec<String>> {
251 &self.export_patterns
252 }
253
254 pub fn imports(&self) -> &MetadataField<Vec<NamespaceImport>> {
255 &self.imports
256 }
257
258 pub fn s3_registrations(&self) -> &MetadataField<Vec<S3Registration>> {
259 &self.s3_registrations
260 }
261
262 pub fn s3_generic_evidence(&self) -> &MetadataField<Vec<String>> {
263 &self.s3_generic_evidence
264 }
265
266 pub fn export_classes(&self) -> &MetadataField<Vec<String>> {
267 &self.export_classes
268 }
269
270 pub fn export_methods(&self) -> &MetadataField<Vec<String>> {
271 &self.export_methods
272 }
273
274 pub fn export_class_patterns(&self) -> &MetadataField<Vec<String>> {
275 &self.export_class_patterns
276 }
277}
278
279impl TryFrom<&RObject> for NamespaceMetadata {
280 type Error = ViewError;
281
282 fn try_from(value: &RObject) -> Result<Self, Self::Error> {
283 Self::from_object(value)
284 }
285}
286
287enum ParseFailure {
288 Invalid(ViewError),
289 Unsupported(String),
290}
291
292fn parse_known_field<T, F>(fields: &[(String, &RObject)], name: &str, parser: F) -> MetadataField<T>
293where
294 F: FnOnce(&RObject, &str) -> Result<T, ParseFailure>,
295{
296 let mut matches = fields.iter().filter(|(field, _)| field == name);
297 let Some((_, object)) = matches.next() else {
298 return MetadataField::Missing;
299 };
300 if matches.next().is_some() {
301 return MetadataField::Invalid(super::duplicate(
302 &format!("NamespaceMetadata.{name}"),
303 Some(name.to_owned()),
304 ));
305 }
306 match parser(object, &format!("NamespaceMetadata.{name}")) {
307 Ok(value) => MetadataField::Present(value),
308 Err(ParseFailure::Invalid(error)) => MetadataField::Invalid(error),
309 Err(ParseFailure::Unsupported(description)) => {
310 MetadataField::UnsupportedSchema { description }
311 }
312 }
313}
314
315fn parse_exports(object: &RObject, path: &str) -> Result<Vec<NamespaceExport>, ParseFailure> {
316 let values = match object.value() {
317 RValue::Character(values) => values,
318 value => {
319 return Err(ParseFailure::Invalid(unexpected_type(
320 path,
321 None,
322 "character vector",
323 value.kind_name(),
324 )));
325 }
326 };
327 let aliases = match object.attributes().get("names") {
328 None => None,
329 Some(attribute) => match &attribute.value() {
330 RValue::Character(names) => Some(names),
331 value => {
332 return Err(ParseFailure::Invalid(unexpected_type(
333 &format!("{path}.names"),
334 None,
335 "character vector",
336 value.kind_name(),
337 )));
338 }
339 },
340 };
341 if let Some(aliases) = aliases
342 && aliases.len() != values.len()
343 {
344 return Err(ParseFailure::Invalid(unexpected_length(
345 &format!("{path}.names"),
346 None,
347 values.len().to_string(),
348 aliases.len(),
349 )));
350 }
351 values
352 .iter()
353 .enumerate()
354 .map(|(index, value)| {
355 let source_name = decode_required(value, &format!("{path}[{index}]"), None)
356 .map_err(ParseFailure::Invalid)?;
357 let exported_name = match aliases {
358 Some(aliases) => {
359 let alias =
360 decode_required(&aliases[index], &format!("{path}.names[{index}]"), None)
361 .map_err(ParseFailure::Invalid)?;
362 if alias.is_empty() {
363 source_name.clone()
364 } else {
365 alias
366 }
367 }
368 None => source_name.clone(),
369 };
370 Ok(NamespaceExport {
371 source_name,
372 exported_name,
373 })
374 })
375 .collect()
376}
377
378fn parse_strings(object: &RObject, path: &str) -> Result<Vec<String>, ParseFailure> {
379 let values = match object.value() {
380 RValue::Character(values) => values,
381 value => {
382 return Err(ParseFailure::Invalid(unexpected_type(
383 path,
384 None,
385 "character vector",
386 value.kind_name(),
387 )));
388 }
389 };
390 values
391 .iter()
392 .enumerate()
393 .map(|(index, value)| {
394 decode_required(value, &format!("{path}[{index}]"), None).map_err(ParseFailure::Invalid)
395 })
396 .collect()
397}
398
399fn parse_imports(object: &RObject, path: &str) -> Result<Vec<NamespaceImport>, ParseFailure> {
400 match object.value() {
401 RValue::Character(values) => values
402 .iter()
403 .enumerate()
404 .map(|(index, value)| {
405 let package = decode_required(value, &format!("{path}[{index}]"), None)
406 .map_err(ParseFailure::Invalid)?;
407 Ok(NamespaceImport::All {
408 package,
409 except: Vec::new(),
410 })
411 })
412 .collect(),
413 RValue::List(values) => values
414 .iter()
415 .enumerate()
416 .map(|(index, value)| parse_import_entry(value, &format!("{path}[{index}]")))
417 .collect(),
418 value => Err(ParseFailure::Invalid(unexpected_type(
419 path,
420 None,
421 "character vector or list",
422 value.kind_name(),
423 ))),
424 }
425}
426
427fn parse_import_entry(object: &RObject, path: &str) -> Result<NamespaceImport, ParseFailure> {
428 if let RValue::Character(values) = object.value() {
429 if values.len() != 1 {
430 return Err(ParseFailure::Invalid(unexpected_length(
431 path,
432 None,
433 "1".to_owned(),
434 values.len(),
435 )));
436 }
437 return Ok(NamespaceImport::All {
438 package: decode_required(&values[0], path, None).map_err(ParseFailure::Invalid)?,
439 except: Vec::new(),
440 });
441 }
442
443 let RValue::List(values) = object.value() else {
444 return Err(ParseFailure::Unsupported(format!(
445 "{path} is not a supported import declaration"
446 )));
447 };
448 let names = match object.attributes().get("names") {
449 Some(attribute) => match &attribute.value() {
450 RValue::Character(names) => Some(names),
451 value => {
452 return Err(ParseFailure::Invalid(unexpected_type(
453 &format!("{path}.names"),
454 None,
455 "character vector",
456 value.kind_name(),
457 )));
458 }
459 },
460 None => None,
461 };
462 let Some(names) = names else {
463 if values.len() != 2 {
465 return Err(ParseFailure::Unsupported(format!(
466 "{path} does not contain a supported positional import declaration"
467 )));
468 }
469 let package = parse_package_scalar(&values[0], &format!("{path}[0]"))?;
470 let selections = parse_imported_names(&values[1], &format!("{path}[1]"))?;
471 return Ok(NamespaceImport::From {
472 package,
473 names: selections,
474 });
475 };
476
477 if names.len() != values.len() {
478 return Err(ParseFailure::Invalid(unexpected_length(
479 &format!("{path}.names"),
480 None,
481 values.len().to_string(),
482 names.len(),
483 )));
484 }
485
486 let mut decoded_names = Vec::with_capacity(names.len());
492 let mut empty_name_indices = Vec::new();
493 for (index, name) in names.iter().enumerate() {
494 let decoded = decode_required(name, &format!("{path}.names[{index}]"), None)
495 .map_err(ParseFailure::Invalid)?;
496 if decoded.is_empty() {
497 empty_name_indices.push(index);
498 decoded_names.push(decoded);
499 continue;
500 }
501 if !matches!(
502 decoded.as_str(),
503 "package" | "except" | "selections" | "names"
504 ) {
505 return Err(ParseFailure::Unsupported(format!(
506 "{path} contains unsupported import field {decoded:?}"
507 )));
508 }
509 if decoded_names.iter().any(|existing| existing == &decoded) {
510 return Err(ParseFailure::Invalid(super::duplicate(
511 &format!("{path}.names"),
512 Some(decoded),
513 )));
514 }
515 decoded_names.push(decoded);
516 }
517
518 if empty_name_indices != [0] && !empty_name_indices.is_empty() {
519 return Err(ParseFailure::Unsupported(format!(
520 "{path} has an unnamed import field outside the first position"
521 )));
522 }
523 let named_package_index = decoded_names.iter().position(|name| name == "package");
524 if !empty_name_indices.is_empty() && named_package_index.is_some() {
525 return Err(ParseFailure::Invalid(super::duplicate(
526 &format!("{path}.names"),
527 Some("package".into()),
528 )));
529 }
530 let package_index = empty_name_indices.first().copied().or(named_package_index);
531 let Some(package_index) = package_index else {
532 return Err(ParseFailure::Unsupported(format!(
533 "{path} does not contain a package entry"
534 )));
535 };
536 let package =
537 parse_package_scalar(&values[package_index], &format!("{path}[{package_index}]"))?;
538
539 let except_index = decoded_names.iter().position(|name| name == "except");
540 let selection_indices: Vec<_> = decoded_names
541 .iter()
542 .enumerate()
543 .filter_map(|(index, name)| {
544 matches!(name.as_str(), "selections" | "names").then_some(index)
545 })
546 .collect();
547 if selection_indices.len() > 1 {
548 return Err(ParseFailure::Invalid(unexpected_type(
549 &format!("{path}.names"),
550 None,
551 "one import selection field",
552 "multiple import selection fields",
553 )));
554 }
555 if except_index.is_some() && !selection_indices.is_empty() {
556 return Err(ParseFailure::Invalid(unexpected_type(
557 &format!("{path}.names"),
558 None,
559 "either except or selections/names",
560 "both except and selections/names",
561 )));
562 }
563 if let Some(except_index) = except_index {
564 let except =
565 parse_string_vector(&values[except_index], &format!("{path}[{except_index}]"))?;
566 return Ok(NamespaceImport::All { package, except });
567 }
568
569 let Some(selection_index) = selection_indices.first().copied() else {
570 return Err(ParseFailure::Unsupported(format!(
571 "{path} does not contain import selections or except"
572 )));
573 };
574 let selections = parse_imported_names(
575 &values[selection_index],
576 &format!("{path}[{selection_index}]"),
577 )?;
578 Ok(NamespaceImport::From {
579 package,
580 names: selections,
581 })
582}
583
584fn parse_package_scalar(object: &RObject, path: &str) -> Result<String, ParseFailure> {
585 let RValue::Character(values) = object.value() else {
586 return Err(ParseFailure::Invalid(unexpected_type(
587 path,
588 None,
589 "character scalar",
590 object.value().kind_name(),
591 )));
592 };
593 if values.len() != 1 {
594 return Err(ParseFailure::Invalid(unexpected_length(
595 path,
596 None,
597 "1".to_owned(),
598 values.len(),
599 )));
600 }
601 decode_required(&values[0], path, None).map_err(ParseFailure::Invalid)
602}
603
604fn parse_string_vector(object: &RObject, path: &str) -> Result<Vec<String>, ParseFailure> {
605 parse_strings(object, path)
606}
607
608fn parse_imported_names(object: &RObject, path: &str) -> Result<Vec<ImportedName>, ParseFailure> {
609 let RValue::Character(values) = object.value() else {
610 return Err(ParseFailure::Invalid(unexpected_type(
611 path,
612 None,
613 "character vector",
614 object.value().kind_name(),
615 )));
616 };
617 let aliases = match object.attributes().get("names") {
618 None => None,
619 Some(attribute) => match &attribute.value() {
620 RValue::Character(values) => Some(values),
621 value => {
622 return Err(ParseFailure::Invalid(unexpected_type(
623 &format!("{path}.names"),
624 None,
625 "character vector",
626 value.kind_name(),
627 )));
628 }
629 },
630 };
631 if let Some(aliases) = aliases
632 && aliases.len() != values.len()
633 {
634 return Err(ParseFailure::Invalid(unexpected_length(
635 &format!("{path}.names"),
636 None,
637 values.len().to_string(),
638 aliases.len(),
639 )));
640 }
641 values
642 .iter()
643 .enumerate()
644 .map(|(index, value)| {
645 let source_name = decode_required(value, &format!("{path}[{index}]"), None)
646 .map_err(ParseFailure::Invalid)?;
647 let local_name = match aliases {
648 Some(aliases) => {
649 let alias =
650 decode_required(&aliases[index], &format!("{path}.names[{index}]"), None)
651 .map_err(ParseFailure::Invalid)?;
652 if alias.is_empty() {
653 source_name.clone()
654 } else {
655 alias
656 }
657 }
658 None => source_name.clone(),
659 };
660 Ok(ImportedName {
661 source_name,
662 local_name,
663 })
664 })
665 .collect()
666}
667
668fn parse_s3_registrations(
669 object: &RObject,
670 path: &str,
671) -> Result<Vec<S3Registration>, ParseFailure> {
672 let matrix = parse_character_matrix(object, path)?;
673 if matrix.ncol == 0 {
674 return Err(ParseFailure::Invalid(invalid_dimensions(
675 path,
676 "S3 registration matrix must have at least one column",
677 )));
678 }
679 if matrix.ncol != 4 {
680 return Err(ParseFailure::Unsupported(format!(
681 "S3 registration matrix has {} columns; only the four-column schema is supported",
682 matrix.ncol
683 )));
684 }
685 (0..matrix.nrow)
686 .map(|row| {
687 let generic = matrix.required(row, 0, path)?;
688 let class = matrix.required(row, 1, path)?;
689 let method = match matrix.optional(row, 2, path)? {
690 Some(value) => S3MethodName::Explicit(value),
691 None => S3MethodName::Implicit,
692 };
693 let generic_package = matrix.optional(row, 3, path)?;
694 Ok(S3Registration {
695 generic,
696 class,
697 method,
698 generic_package,
699 })
700 })
701 .collect()
702}
703
704fn parse_s3_generic_evidence(object: &RObject, path: &str) -> Result<Vec<String>, ParseFailure> {
705 let matrix = parse_character_matrix(object, path)?;
706 if matrix.ncol == 0 {
707 return Err(ParseFailure::Invalid(invalid_dimensions(
708 path,
709 "S3 metadata matrix must have at least one column",
710 )));
711 }
712 (0..matrix.nrow)
713 .map(|row| matrix.required(row, 0, path))
714 .collect()
715}
716
717struct CharacterMatrix<'a> {
718 values: &'a [RStr],
719 nrow: usize,
720 ncol: usize,
721}
722
723impl CharacterMatrix<'_> {
724 fn at(&self, row: usize, column: usize) -> &RStr {
725 &self.values[row + column * self.nrow]
726 }
727
728 fn required(&self, row: usize, column: usize, path: &str) -> Result<String, ParseFailure> {
729 decode_required(
730 self.at(row, column),
731 &format!("{path}[row={row},column={column}]"),
732 None,
733 )
734 .map_err(ParseFailure::Invalid)
735 }
736
737 fn optional(
738 &self,
739 row: usize,
740 column: usize,
741 path: &str,
742 ) -> Result<Option<String>, ParseFailure> {
743 decode_optional(
744 self.at(row, column),
745 &format!("{path}[row={row},column={column}]"),
746 None,
747 )
748 .map_err(ParseFailure::Invalid)
749 }
750}
751
752fn parse_character_matrix<'a>(
753 object: &'a RObject,
754 path: &str,
755) -> Result<CharacterMatrix<'a>, ParseFailure> {
756 let values = match object.value() {
757 RValue::Character(values) => values,
758 value => {
759 return Err(ParseFailure::Invalid(unexpected_type(
760 path,
761 None,
762 "character matrix",
763 value.kind_name(),
764 )));
765 }
766 };
767 let dimensions = object.attributes().get("dim").ok_or_else(|| {
768 ParseFailure::Invalid(super::missing(format!("{path}.attributes.dim"), None))
769 })?;
770 let RValue::Integer(dimensions) = dimensions.value() else {
771 return Err(ParseFailure::Invalid(unexpected_type(
772 &format!("{path}.attributes.dim"),
773 None,
774 "integer vector",
775 dimensions.value().kind_name(),
776 )));
777 };
778 if dimensions.len() != 2 {
779 return Err(ParseFailure::Invalid(unexpected_length(
780 &format!("{path}.attributes.dim"),
781 None,
782 "2".to_owned(),
783 dimensions.len(),
784 )));
785 }
786 let mut shape = [0usize; 2];
787 for (index, value) in dimensions.iter().enumerate() {
788 let Some(value) = value else {
789 return Err(ParseFailure::Invalid(invalid_dimensions(
790 &format!("{path}.attributes.dim"),
791 "dimensions must not contain NA",
792 )));
793 };
794 if *value < 0 {
795 return Err(ParseFailure::Invalid(invalid_dimensions(
796 &format!("{path}.attributes.dim"),
797 "dimensions must not be negative",
798 )));
799 }
800 shape[index] = *value as usize;
801 }
802 let expected = shape[0].checked_mul(shape[1]).ok_or_else(|| {
803 ParseFailure::Invalid(invalid_dimensions(
804 &format!("{path}.attributes.dim"),
805 "dimension product overflows",
806 ))
807 })?;
808 if expected != values.len() {
809 return Err(ParseFailure::Invalid(unexpected_length(
810 path,
811 None,
812 expected.to_string(),
813 values.len(),
814 )));
815 }
816 Ok(CharacterMatrix {
817 values,
818 nrow: shape[0],
819 ncol: shape[1],
820 })
821}
822
823impl fmt::Display for S3MethodName {
824 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
825 match self {
826 Self::Implicit => formatter.write_str("implicit"),
827 Self::Explicit(value) => formatter.write_str(value),
828 }
829 }
830}