1use serde::{Deserialize, Serialize};
8use std::borrow::Borrow;
9use std::fmt;
10use std::ops::Deref;
11use std::path::{Path, PathBuf};
12
13pub use amql_mutate::{NodeKind, RelativePath};
16pub use amql_selector::{AttrName, TagName};
17
18macro_rules! define_newtype_string {
19 ($name:ident, $doc:expr) => {
20 #[doc = $doc]
21 #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
22 #[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
23 #[cfg_attr(feature = "ts", derive(ts_rs::TS))]
24 #[cfg_attr(feature = "flow", derive(flowjs_rs::Flow))]
25 #[cfg_attr(feature = "ts", ts(export))]
26 #[cfg_attr(feature = "flow", flow(export))]
27 #[serde(transparent)]
28 pub struct $name(String);
29
30 impl Deref for $name {
31 type Target = str;
32 #[inline]
33 fn deref(&self) -> &str {
34 &self.0
35 }
36 }
37
38 impl AsRef<str> for $name {
39 #[inline]
40 fn as_ref(&self) -> &str {
41 &self.0
42 }
43 }
44
45 impl Borrow<str> for $name {
46 #[inline]
47 fn borrow(&self) -> &str {
48 &self.0
49 }
50 }
51
52 impl fmt::Display for $name {
53 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54 f.write_str(&self.0)
55 }
56 }
57
58 impl From<String> for $name {
59 #[inline]
60 fn from(s: String) -> Self {
61 Self(s)
62 }
63 }
64
65 impl From<&str> for $name {
66 #[inline]
67 fn from(s: &str) -> Self {
68 Self(s.to_owned())
69 }
70 }
71
72 impl PartialEq<str> for $name {
73 #[inline]
74 fn eq(&self, other: &str) -> bool {
75 self.0 == other
76 }
77 }
78
79 impl PartialEq<&str> for $name {
80 #[inline]
81 fn eq(&self, other: &&str) -> bool {
82 self.0 == *other
83 }
84 }
85
86 impl PartialEq<String> for $name {
87 #[inline]
88 fn eq(&self, other: &String) -> bool {
89 self.0 == *other
90 }
91 }
92 };
93}
94
95define_newtype_string!(Binding, "Annotation-to-code join key.");
96define_newtype_string!(Scope, "Query scope prefix (directory, file, or empty).");
97define_newtype_string!(CodeElementName, "Name of a code construct.");
98define_newtype_string!(SelectorStr, "Raw selector string before parsing.");
99
100#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
106#[cfg_attr(feature = "ts", derive(ts_rs::TS))]
107#[cfg_attr(feature = "flow", derive(flowjs_rs::Flow))]
108#[cfg_attr(feature = "ts", ts(export))]
109#[cfg_attr(feature = "flow", flow(export))]
110#[serde(transparent)]
111pub struct ProjectRoot(PathBuf);
112
113impl Deref for ProjectRoot {
114 type Target = Path;
115 #[inline]
116 fn deref(&self) -> &Path {
117 &self.0
118 }
119}
120
121impl AsRef<Path> for ProjectRoot {
122 #[inline]
123 fn as_ref(&self) -> &Path {
124 &self.0
125 }
126}
127
128impl fmt::Display for ProjectRoot {
129 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
130 write!(f, "{}", self.0.display())
131 }
132}
133
134impl From<PathBuf> for ProjectRoot {
135 #[inline]
136 fn from(p: PathBuf) -> Self {
137 Self(p)
138 }
139}
140
141impl From<&Path> for ProjectRoot {
142 #[inline]
143 fn from(p: &Path) -> Self {
144 Self(p.to_path_buf())
145 }
146}
147
148impl ProjectRoot {
149 #[inline]
151 pub fn join<P: AsRef<Path>>(&self, path: P) -> PathBuf {
152 self.0.join(path)
153 }
154
155 #[inline]
157 pub fn to_path_buf(&self) -> PathBuf {
158 self.0.clone()
159 }
160}
161
162#[cfg(test)]
163mod tests {
164 use super::*;
165 use rustc_hash::FxHashMap;
166
167 #[test]
168 fn string_newtype_behavior() {
169 let tag = TagName::from("function");
171 let opt_tag: Option<TagName> = Some(TagName::from("function"));
172 let mut map = FxHashMap::default();
173 map.insert(TagName::from("function"), 42);
174
175 let deref_val: &str = &tag;
177 let display_val = format!("{tag}");
178 let lookup_val = map.get("function");
179 let as_deref_val = opt_tag.as_deref();
180
181 assert_eq!(deref_val, "function", "Deref should yield inner str");
183 assert_eq!(display_val, "function", "Display should show inner value");
184 assert_eq!(tag, "function", "PartialEq<&str> should work");
185 assert_eq!(tag, *"function", "PartialEq<str> should work");
186 assert_eq!(tag, "function".to_string(), "PartialEq<String> should work");
187 assert_eq!(lookup_val, Some(&42), "Borrow<str> should allow map lookup");
188 assert_eq!(
189 as_deref_val,
190 Some("function"),
191 "Option::as_deref should work"
192 );
193 }
194
195 #[test]
196 fn project_root_behavior() {
197 let root = ProjectRoot::from(PathBuf::from("/project"));
199
200 let joined = root.join("src/main.rs");
202 let display_val = format!("{root}");
203
204 assert_eq!(
206 joined,
207 PathBuf::from("/project/src/main.rs"),
208 "join should produce correct path"
209 );
210 assert_eq!(display_val, "/project", "Display should show path");
211 }
212}