Skip to main content

bitwarden_importers/importers/onepassword/convert/
claimed.rs

1//! How an item's fields are addressed: walking them, reading one by name, and recording which a
2//! category mapping already took.
3
4use super::value::{non_blank, render_value};
5use crate::importers::onepassword::access::wire::{VaultItemDetails, VaultItemSectionField};
6
7/// What a category mapping already read, so the leftover pass does not repeat it.
8#[derive(Default)]
9pub(super) struct Claimed {
10    /// Designations of consumed top-level fields.
11    pub(super) designations: &'static [&'static str],
12    /// Positions of consumed section fields, as [`section_fields`] numbers them. An id would not
13    /// do: a mapping picks one field, and only its position says which.
14    pub(super) fields: Vec<usize>,
15}
16
17impl Claimed {
18    /// Reads the section field with this id and claims it, provided `read` makes something of it.
19    /// A field `read` turns down stays for the leftover pass.
20    pub(super) fn take<'a, T>(
21        &mut self,
22        details: &'a VaultItemDetails,
23        id: &str,
24        read: impl FnOnce(&'a VaultItemSectionField) -> Option<T>,
25    ) -> Option<T> {
26        let (position, field) = section_fields(details)
27            .enumerate()
28            .find(|(_, field)| field.id.as_deref() == Some(id))?;
29        let value = read(field)?;
30        self.fields.push(position);
31        Some(value)
32    }
33
34    /// Reads the section field with this id as the text the leftover pass would have made of it,
35    /// and claims it.
36    pub(super) fn take_text(&mut self, details: &VaultItemDetails, id: &str) -> Option<String> {
37        self.take(details, id, |field| {
38            render_value(field.kind.as_deref(), field.value.as_ref()?)
39        })
40    }
41}
42
43/// Every section field of an item, flattened in the order 1Password sent them. A mapping and the
44/// leftover pass walk this same sequence, so a position names one field for both.
45pub(super) fn section_fields(
46    details: &VaultItemDetails,
47) -> impl Iterator<Item = &VaultItemSectionField> {
48    details
49        .sections
50        .iter()
51        .flatten()
52        .flat_map(|section| section.fields.iter().flatten())
53}
54
55/// Reads one of the login fields 1Password tags with a `designation`. The designation is stable,
56/// unlike the field's localized `name`.
57pub(super) fn designation(details: &VaultItemDetails, designation: &str) -> Option<String> {
58    details
59        .fields
60        .iter()
61        .flatten()
62        .find(|field| field.designation.as_deref() == Some(designation))
63        .and_then(|field| field.value.as_deref())
64        .and_then(non_blank)
65        .map(str::to_string)
66}