Skip to main content

selectors_ext/
inspect.rs

1// Copyright 2026 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
5//! A serde module for Inspect selectors, to be used in the "with" field
6//! attribute.
7//!
8//! ```
9//! #[derive(Serialize, Deserialize)]
10//! struct Config {
11//!     #[serde(with="selectors_ext::inspect")]
12//!     selectors: Vec<fidl_fuchsia_diagnostics::Selector>,
13//! }
14//! ```
15
16use serde::de::{Error, SeqAccess, Visitor};
17use serde::ser::SerializeSeq;
18use serde::{Deserializer, Serializer};
19
20const PREFIX: &str = "INSPECT:";
21
22pub fn serialize<S>(
23    selectors: &Vec<fidl_fuchsia_diagnostics::Selector>,
24    serializer: S,
25) -> Result<S::Ok, S::Error>
26where
27    S: Serializer,
28{
29    let mut seq = serializer.serialize_seq(Some(selectors.len()))?;
30
31    for selector in selectors {
32        seq.serialize_element(&format!(
33            "{PREFIX}{}",
34            selectors::selector_to_string(selector, selectors::SelectorDisplayOptions::default())
35                .map_err(serde::ser::Error::custom)?
36        ))?;
37    }
38
39    seq.end()
40}
41
42pub fn deserialize<'de, D>(
43    deserializer: D,
44) -> Result<Vec<fidl_fuchsia_diagnostics::Selector>, D::Error>
45where
46    D: Deserializer<'de>,
47{
48    deserializer.deserialize_seq(SelectorsVisitor)
49}
50
51struct SelectorsVisitor;
52
53impl<'de> Visitor<'de> for SelectorsVisitor {
54    type Value = Vec<fidl_fuchsia_diagnostics::Selector>;
55
56    fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57        write!(formatter, "should be a list of string selectors starting with {}", PREFIX)
58    }
59
60    fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
61    where
62        A: SeqAccess<'de>,
63    {
64        let mut selectors = Vec::new();
65        while let Some(v) = seq.next_element::<String>()? {
66            let Some(rest) = v.strip_prefix(PREFIX) else {
67                return Err(A::Error::custom(format!(
68                    "Expected selector with prefix \"{}\", got \"{v}\"",
69                    PREFIX
70                )));
71            };
72            selectors.push(
73                selectors::parse_selector::<selectors::VerboseError>(rest)
74                    .map_err(|e| A::Error::custom(e))?,
75            );
76        }
77        Ok(selectors)
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use super::*;
84    use fidl_fuchsia_diagnostics::Selector;
85    use pretty_assertions::assert_eq;
86    use serde::{Deserialize, Serialize};
87    use test_case::test_case;
88
89    #[derive(Debug, Serialize, Deserialize, PartialEq)]
90    #[serde(transparent)]
91    struct TestConfig {
92        #[serde(with = "crate::inspect")]
93        selectors: Vec<Selector>,
94    }
95
96    #[test_case("a/b:c:d" ; "with_basic_full_selector")]
97    #[test_case("a/b:c" ; "with_basic_partial_selector")]
98    #[test_case(r"a/b:c/d\/e:f" ; "with_escaped_forward_slash")]
99    #[test_case(r"a/b:[name=cd-e]f:g" ; "with_non_default_name")]
100    #[test_case(r#"a:[name="bc-d"]e:f"# ; "with_unneeded_name_quotes")]
101    #[test_case(r#"a:[name="b[]c"]d:e"# ; "with_needed_name_quotes")]
102    #[test_case("a/b:[...]c:d" ; "with_all_names")]
103    #[test_case(r#"a/b:[name=c, name="d", name="f[]g"]h:i"# ; "with_name_list")]
104    #[test_case(r"a\:b/c:d:e" ; "with_collection")]
105    #[test_case(r"a/b/c*d:e:f" ; "with_wildcard_component")]
106    #[test_case(r"a/b:c*/d:e" ; "with_wildcard_tree")]
107    #[test_case(r"a/b:c\*/d:e" ; "with_escaped_wildcard_tree")]
108    #[test_case(r"a/b/c/d:e/f:g*" ; "with_wildcard_property")]
109    #[test_case("a/b/c/d:e/f/g/h:k" ; "with_deep_nesting")]
110    #[fuchsia::test]
111    fn test_serialization(selector: &str) {
112        // Escape double quotes and forward slashes then wrap in double quotes.
113        let selector_escaped = serde_json::to_string(&format!("{PREFIX}{selector}")).unwrap();
114
115        let deserialized: TestConfig =
116            serde_json::from_str(&format!("[{selector_escaped}]")).unwrap();
117        let serialized = serde_json::to_string(&deserialized).unwrap();
118        let deserialized_again: TestConfig = serde_json::from_str(&serialized).unwrap();
119        assert_eq!(deserialized, deserialized_again);
120        assert_eq!(deserialized.selectors, vec![selectors::parse_verbose(selector).unwrap()]);
121    }
122
123    #[test_case("🦀a🦀" ; "unicode_split_at_boundary_emoji")]
124    #[test_case("日本語" ; "unicode_split_at_boundary_cjk")]
125    #[test_case("test:🔥" ; "unicode_split_at_boundary_short_prefix")]
126    #[fuchsia::test]
127    fn test_deserialization_invalid_prefix_unicode(input: &str) {
128        let json = serde_json::to_string(&vec![input]).unwrap();
129        let result = serde_json::from_str::<TestConfig>(&json);
130        assert!(result.is_err());
131    }
132}