inspect_fetcher/
lib.rs

1// Copyright 2021 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use anyhow::{Error, bail};
6use diagnostics_reader::{ArchiveReader, InspectArchiveReader, RetryConfig};
7use log::warn;
8
9// Selectors for Inspect data must start with this exact string.
10const INSPECT_PREFIX: &str = "INSPECT:";
11
12/// `InspectFetcher` fetches data from a list of selectors from ArchiveAccessor.
13pub struct InspectFetcher {
14    // If we have no selectors, we don't want to actually fetch anything.
15    // (Fetching with no selectors fetches all Inspect data.)
16    reader: Option<InspectArchiveReader>,
17}
18
19impl std::fmt::Debug for InspectFetcher {
20    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
21        f.debug_struct("InspectFetcher").field("reader", &"opaque-ArchiveReader").finish()
22    }
23}
24
25impl InspectFetcher {
26    /// Creates an InspectFetcher or returns an error. Note: If no selectors are given,
27    /// fetch() will return "[]" instead of fetching all Inspect data.
28    ///
29    /// `service_path` should name a fuchsia.diagnostics.ArchiveAccessor service.
30    /// `selectors` should be in Triage format, i.e. INSPECT:moniker:path:leaf.
31    pub fn create(service_path: &str, selectors: Vec<String>) -> Result<InspectFetcher, Error> {
32        if selectors.is_empty() {
33            return Ok(InspectFetcher { reader: None });
34        }
35        let proxy = match fuchsia_component::client::connect_to_protocol_at_path::<
36            fidl_fuchsia_diagnostics::ArchiveAccessorMarker,
37        >(service_path)
38        {
39            Ok(proxy) => proxy,
40            Err(e) => bail!("Failed to connect to Inspect reader: {}", e),
41        };
42        let mut reader = ArchiveReader::inspect();
43        reader
44            .with_archive(proxy)
45            .retry(RetryConfig::never())
46            .add_selectors(Self::process_selectors(selectors)?.into_iter());
47        Ok(InspectFetcher { reader: Some(reader) })
48    }
49
50    /// Fetches the selectee Inspect data.
51    /// Data is returned as a String in JSON format because that's what TriageLib needs.
52    pub async fn fetch(&mut self) -> Result<String, Error> {
53        match &self.reader {
54            None => Ok("[]".to_string()),
55            Some(reader) => {
56                // TODO(https://fxbug.dev/42140879): Make TriageLib accept structured data
57                Ok(reader.snapshot_raw::<serde_json::Value>().await?.to_string())
58            }
59        }
60    }
61
62    fn process_selectors(selectors: Vec<String>) -> Result<Vec<String>, Error> {
63        Ok(selectors.into_iter().filter_map(remove_inspect_prefix).collect())
64    }
65}
66
67/// Remove the "INSPECT:" prefix from selectors. Returns None if the Inspect
68/// prefix is not found.
69pub fn remove_inspect_prefix(mut s: String) -> Option<String> {
70    if s.len() >= INSPECT_PREFIX.len() && s[..INSPECT_PREFIX.len()] == *INSPECT_PREFIX {
71        s.replace_range(0..INSPECT_PREFIX.len(), "");
72        Some(s)
73    } else {
74        warn!("All Inspect selectors should begin with 'INSPECT:' - '{}'", s);
75        None
76    }
77}
78
79#[cfg(test)]
80mod tests {
81    use super::*;
82
83    #[fuchsia::test]
84    async fn test_selector_acceptance() {
85        let empty_vec = vec![];
86        let ok_selectors =
87            vec!["INSPECT:moniker:path:leaf".to_string(), "INSPECT:name:nodes:item".to_string()];
88        let ok_processed = vec!["moniker:path:leaf".to_string(), "name:nodes:item".to_string()];
89
90        let bad_selector = vec![
91            "INSPECT:moniker:path:leaf".to_string(),
92            "FOO:moniker:path:leaf".to_string(),
93            "INSPECT:name:nodes:item".to_string(),
94        ];
95
96        assert_eq!(InspectFetcher::process_selectors(empty_vec).unwrap(), Vec::<String>::new());
97        assert_eq!(InspectFetcher::process_selectors(ok_selectors).unwrap(), ok_processed);
98        assert_eq!(InspectFetcher::process_selectors(bad_selector).unwrap(), ok_processed);
99    }
100}