greentic_types/
state.rs

1//! State key and JSON pointer helpers.
2
3use alloc::string::String;
4use alloc::vec::Vec;
5
6#[cfg(feature = "schemars")]
7use schemars::JsonSchema;
8#[cfg(feature = "serde")]
9use serde::{Deserialize, Serialize};
10
11/// Unique key referencing a persisted state blob.
12#[derive(Clone, Debug, PartialEq, Eq, Hash)]
13#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
14#[cfg_attr(feature = "schemars", derive(JsonSchema))]
15#[cfg_attr(feature = "serde", serde(transparent))]
16pub struct StateKey(pub String);
17
18impl StateKey {
19    /// Returns the key as a string slice.
20    pub fn as_str(&self) -> &str {
21        &self.0
22    }
23
24    /// Creates a new state key from the provided value.
25    pub fn new(value: impl Into<String>) -> Self {
26        Self(value.into())
27    }
28}
29
30impl From<String> for StateKey {
31    fn from(value: String) -> Self {
32        Self(value)
33    }
34}
35
36impl From<&str> for StateKey {
37    fn from(value: &str) -> Self {
38        Self(value.to_owned())
39    }
40}
41
42impl core::fmt::Display for StateKey {
43    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
44        f.write_str(self.as_str())
45    }
46}
47
48/// Hierarchical pointer addressing a nested state path.
49#[derive(Clone, Debug, PartialEq, Eq, Hash)]
50#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
51#[cfg_attr(feature = "schemars", derive(JsonSchema))]
52pub struct StatePath {
53    /// Pointer segments making up the path.
54    #[cfg_attr(feature = "serde", serde(default))]
55    pub segments: Vec<String>,
56}
57
58impl StatePath {
59    /// Creates an empty root path.
60    pub fn root() -> Self {
61        Self {
62            segments: Vec::new(),
63        }
64    }
65
66    /// Pushes a new segment to the path.
67    pub fn push(&mut self, segment: impl Into<String>) {
68        self.segments.push(segment.into());
69    }
70
71    /// Returns a JSON pointer representation (`/a/b/c`).
72    pub fn to_pointer(&self) -> String {
73        if self.segments.is_empty() {
74            return "/".to_owned();
75        }
76
77        let mut pointer = String::new();
78        for segment in &self.segments {
79            pointer.push('/');
80            pointer.push_str(&escape_segment(segment));
81        }
82        pointer
83    }
84
85    /// Creates a path from a JSON pointer representation.
86    pub fn from_pointer(pointer: &str) -> Self {
87        if pointer == "/" || pointer.is_empty() {
88            return Self::root();
89        }
90
91        let segments = pointer
92            .split('/')
93            .skip_while(|s| s.is_empty())
94            .map(unescape_segment)
95            .collect::<Vec<_>>();
96
97        Self { segments }
98    }
99}
100
101impl Default for StatePath {
102    fn default() -> Self {
103        Self::root()
104    }
105}
106
107fn escape_segment(segment: &str) -> String {
108    segment.replace('~', "~0").replace('/', "~1")
109}
110
111fn unescape_segment(segment: &str) -> String {
112    segment.replace("~1", "/").replace("~0", "~")
113}