Skip to main content

rd_rds/package/
packages.rs

1use super::{
2    ValueKindName, ViewError, decode_required, duplicate, invalid_dimensions, missing,
3    unexpected_length, unexpected_type,
4};
5use crate::{RObject, RStr, RValue};
6
7/// A validated, owned view of a CRAN-like `PACKAGES.rds` character matrix.
8#[derive(Debug, Clone, PartialEq, Eq)]
9pub struct PackagesMatrix {
10    nrow: usize,
11    column_names: Vec<String>,
12    cells: Vec<Option<String>>,
13}
14
15impl PackagesMatrix {
16    /// Validates and copies a `PACKAGES.rds` character matrix.
17    pub fn from_object(object: &RObject) -> Result<Self, ViewError> {
18        let values = match &object.value() {
19            RValue::Character(values) => values,
20            value => {
21                return Err(unexpected_type(
22                    "PACKAGES",
23                    None,
24                    "character vector",
25                    value.kind_name(),
26                ));
27            }
28        };
29        let dimensions = object
30            .attributes()
31            .get("dim")
32            .ok_or_else(|| missing("PACKAGES.attributes.dim", None))?;
33        let dimensions = match &dimensions.value() {
34            RValue::Integer(values) => values,
35            value => {
36                return Err(unexpected_type(
37                    "PACKAGES.attributes.dim",
38                    None,
39                    "integer vector",
40                    value.kind_name(),
41                ));
42            }
43        };
44        if dimensions.len() != 2 {
45            return Err(unexpected_length(
46                "PACKAGES.attributes.dim",
47                None,
48                "2".to_owned(),
49                dimensions.len(),
50            ));
51        }
52        let mut shape = [0usize; 2];
53        for (index, value) in dimensions.iter().enumerate() {
54            let Some(value) = value else {
55                return Err(invalid_dimensions(
56                    "PACKAGES.attributes.dim",
57                    "dimensions must not contain NA",
58                ));
59            };
60            if *value < 0 {
61                return Err(invalid_dimensions(
62                    "PACKAGES.attributes.dim",
63                    "dimensions must not be negative",
64                ));
65            }
66            shape[index] = *value as usize;
67        }
68        let element_count = shape[0].checked_mul(shape[1]).ok_or_else(|| {
69            invalid_dimensions("PACKAGES.attributes.dim", "dimension product overflows")
70        })?;
71        if values.len() != element_count {
72            return Err(unexpected_length(
73                "PACKAGES",
74                None,
75                element_count.to_string(),
76                values.len(),
77            ));
78        }
79
80        let dimnames = object
81            .attributes()
82            .get("dimnames")
83            .ok_or_else(|| missing("PACKAGES.attributes.dimnames", None))?;
84        let dimnames = match &dimnames.value() {
85            RValue::List(values) => values,
86            value => {
87                return Err(unexpected_type(
88                    "PACKAGES.attributes.dimnames",
89                    None,
90                    "list",
91                    value.kind_name(),
92                ));
93            }
94        };
95        if dimnames.len() != 2 {
96            return Err(unexpected_length(
97                "PACKAGES.attributes.dimnames",
98                None,
99                "2".to_owned(),
100                dimnames.len(),
101            ));
102        }
103        validate_row_names(&dimnames[0], shape[0])?;
104        let column_values = match &dimnames[1].value() {
105            RValue::Character(values) => values,
106            value => {
107                return Err(unexpected_type(
108                    "PACKAGES.attributes.dimnames[1]",
109                    None,
110                    "character vector",
111                    value.kind_name(),
112                ));
113            }
114        };
115        if column_values.len() != shape[1] {
116            return Err(unexpected_length(
117                "PACKAGES.attributes.dimnames[1]",
118                None,
119                shape[1].to_string(),
120                column_values.len(),
121            ));
122        }
123        let mut column_names = Vec::with_capacity(shape[1]);
124        let mut seen_names = std::collections::BTreeSet::new();
125        for (index, value) in column_values.iter().enumerate() {
126            let name = decode_required(
127                value,
128                &format!("PACKAGES.attributes.dimnames[1][{index}]"),
129                None,
130            )?;
131            if !seen_names.insert(name.clone()) {
132                return Err(duplicate(
133                    &format!("PACKAGES.attributes.dimnames[1][{index}]"),
134                    Some(name),
135                ));
136            }
137            column_names.push(name);
138        }
139
140        let mut cells = Vec::with_capacity(element_count);
141        for row in 0..shape[0] {
142            for column in 0..shape[1] {
143                cells.push(decode_matrix_cell(
144                    &values[row + column * shape[0]],
145                    row,
146                    &column_names[column],
147                )?);
148            }
149        }
150        Ok(Self {
151            nrow: shape[0],
152            column_names,
153            cells,
154        })
155    }
156
157    pub fn len(&self) -> usize {
158        self.nrow
159    }
160    pub fn is_empty(&self) -> bool {
161        self.nrow == 0
162    }
163    pub fn column_names(&self) -> impl ExactSizeIterator<Item = &str> + '_ {
164        self.column_names.iter().map(String::as_str)
165    }
166    pub fn column(&self, name: &str) -> Option<PackagesColumn<'_>> {
167        self.column_names
168            .iter()
169            .position(|column| column == name)
170            .map(|index| PackagesColumn {
171                matrix: self,
172                index,
173            })
174    }
175    pub fn row(&self, index: usize) -> Option<PackagesRow<'_>> {
176        (index < self.nrow).then_some(PackagesRow {
177            matrix: self,
178            index,
179        })
180    }
181    pub fn rows(&self) -> impl ExactSizeIterator<Item = PackagesRow<'_>> + '_ {
182        (0..self.nrow).map(|index| PackagesRow {
183            matrix: self,
184            index,
185        })
186    }
187}
188
189impl TryFrom<&RObject> for PackagesMatrix {
190    type Error = ViewError;
191    fn try_from(value: &RObject) -> Result<Self, Self::Error> {
192        Self::from_object(value)
193    }
194}
195
196/// A row in a validated [`PackagesMatrix`].
197#[derive(Debug, Clone, Copy)]
198pub struct PackagesRow<'a> {
199    matrix: &'a PackagesMatrix,
200    index: usize,
201}
202
203impl<'a> PackagesRow<'a> {
204    pub fn index(&self) -> usize {
205        self.index
206    }
207    pub fn get(&self, column: &str) -> Option<Option<&'a str>> {
208        let column = self
209            .matrix
210            .column_names
211            .iter()
212            .position(|name| name == column)?;
213        Some(self.matrix.cells[self.index * self.matrix.column_names.len() + column].as_deref())
214    }
215}
216
217/// A column in a validated [`PackagesMatrix`].
218#[derive(Debug, Clone, Copy)]
219pub struct PackagesColumn<'a> {
220    matrix: &'a PackagesMatrix,
221    index: usize,
222}
223
224impl<'a> PackagesColumn<'a> {
225    pub fn name(&self) -> &str {
226        &self.matrix.column_names[self.index]
227    }
228    pub fn len(&self) -> usize {
229        self.matrix.nrow
230    }
231    pub fn is_empty(&self) -> bool {
232        self.matrix.is_empty()
233    }
234    pub fn get(&self, row: usize) -> Option<Option<&'a str>> {
235        (row < self.matrix.nrow).then(|| {
236            self.matrix.cells[row * self.matrix.column_names.len() + self.index].as_deref()
237        })
238    }
239}
240
241fn validate_row_names(object: &RObject, expected: usize) -> Result<(), ViewError> {
242    let values = match &object.value() {
243        RValue::Null => return Ok(()),
244        RValue::Character(values) => values,
245        value => {
246            return Err(unexpected_type(
247                "PACKAGES.attributes.dimnames[0]",
248                None,
249                "character vector",
250                value.kind_name(),
251            ));
252        }
253    };
254    if values.len() != expected {
255        return Err(unexpected_length(
256            "PACKAGES.attributes.dimnames[0]",
257            None,
258            expected.to_string(),
259            values.len(),
260        ));
261    }
262    for (index, value) in values.iter().enumerate() {
263        if let Some(Err(_)) = value.as_str() {
264            return Err(ViewError::InvalidStringEncoding {
265                path: format!("PACKAGES.attributes.dimnames[0][{index}]"),
266                field: None,
267                row: None,
268                column: None,
269            });
270        }
271    }
272    Ok(())
273}
274
275fn decode_matrix_cell(value: &RStr, row: usize, column: &str) -> Result<Option<String>, ViewError> {
276    match value.as_str() {
277        None => Ok(None),
278        Some(Ok(value)) => Ok(Some(value.into_owned())),
279        Some(Err(_)) => Err(ViewError::InvalidStringEncoding {
280            path: format!("PACKAGES[row={row},column=\"{column}\"]"),
281            field: Some(column.to_owned()),
282            row: Some(row),
283            column: Some(column.to_owned()),
284        }),
285    }
286}