1use 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#[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 pub fn as_str(&self) -> &str {
21 &self.0
22 }
23
24 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#[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 #[cfg_attr(feature = "serde", serde(default))]
55 pub segments: Vec<String>,
56}
57
58impl StatePath {
59 pub fn root() -> Self {
61 Self {
62 segments: Vec::new(),
63 }
64 }
65
66 pub fn push(&mut self, segment: impl Into<String>) {
68 self.segments.push(segment.into());
69 }
70
71 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 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}