1use corium_query::edn::Edn;
19
20use crate::value::IntoEdn;
21
22#[derive(Clone, Copy, Debug, Eq, PartialEq)]
24enum Limit {
25 At(usize),
27 Unlimited,
29}
30
31#[derive(Clone, Debug, Eq, PartialEq)]
34pub struct Attr {
35 name: String,
36 reverse: bool,
37 as_key: Option<Edn>,
38 limit: Option<Limit>,
39 default: Option<Edn>,
40}
41
42impl Attr {
43 #[must_use]
45 pub fn new(name: impl Into<String>) -> Self {
46 Self {
47 name: name.into(),
48 reverse: false,
49 as_key: None,
50 limit: None,
51 default: None,
52 }
53 }
54
55 #[must_use]
57 pub fn reverse(mut self) -> Self {
58 self.reverse = true;
59 self
60 }
61
62 #[must_use]
64 pub fn as_key(mut self, key: impl IntoEdn) -> Self {
65 self.as_key = Some(key.into_edn());
66 self
67 }
68
69 #[must_use]
71 pub fn limit(mut self, n: usize) -> Self {
72 self.limit = Some(Limit::At(n));
73 self
74 }
75
76 #[must_use]
78 pub fn unlimited(mut self) -> Self {
79 self.limit = Some(Limit::Unlimited);
80 self
81 }
82
83 #[must_use]
85 pub fn default(mut self, value: impl IntoEdn) -> Self {
86 self.default = Some(value.into_edn());
87 self
88 }
89
90 fn keyword(&self) -> Edn {
92 Edn::keyword(&reverse_name(&self.name, self.reverse))
93 }
94
95 fn has_options(&self) -> bool {
96 self.as_key.is_some() || self.limit.is_some() || self.default.is_some()
97 }
98
99 fn to_edn(&self) -> Edn {
100 if !self.has_options() {
101 return self.keyword();
102 }
103 let mut items = vec![self.keyword()];
104 if let Some(key) = &self.as_key {
105 items.push(Edn::keyword("as"));
106 items.push(key.clone());
107 }
108 if let Some(limit) = self.limit {
109 items.push(Edn::keyword("limit"));
110 items.push(match limit {
111 Limit::At(n) => Edn::Long(i64::try_from(n).unwrap_or(i64::MAX)),
112 Limit::Unlimited => Edn::Nil,
113 });
114 }
115 if let Some(default) = &self.default {
116 items.push(Edn::keyword("default"));
117 items.push(default.clone());
118 }
119 Edn::Vector(items)
120 }
121}
122
123impl From<&str> for Attr {
124 fn from(value: &str) -> Self {
125 Self::new(value)
126 }
127}
128
129impl From<String> for Attr {
130 fn from(value: String) -> Self {
131 Self::new(value)
132 }
133}
134
135fn reverse_name(name: &str, reverse: bool) -> String {
137 if !reverse {
138 return name.to_owned();
139 }
140 match name.rsplit_once('/') {
141 Some((namespace, local)) => format!("{namespace}/_{local}"),
142 None => format!("_{name}"),
143 }
144}
145
146#[derive(Clone, Debug, Eq, PartialEq)]
147enum Item {
148 Wildcard,
149 DbId,
150 Attr(Attr),
151 Nested(Attr, Pull),
152 Recurse(Attr, Option<usize>),
153}
154
155impl Item {
156 fn to_edn(&self) -> Edn {
157 match self {
158 Self::Wildcard => Edn::symbol("*"),
159 Self::DbId => Edn::keyword("db/id"),
160 Self::Attr(attr) => attr.to_edn(),
161 Self::Nested(attr, sub) => Edn::Map(vec![(attr.to_edn(), sub.to_edn())]),
162 Self::Recurse(attr, depth) => {
163 let value = depth.map_or_else(
164 || Edn::symbol("..."),
165 |depth| Edn::Long(i64::try_from(depth).unwrap_or(i64::MAX)),
166 );
167 Edn::Map(vec![(attr.to_edn(), value)])
168 }
169 }
170 }
171}
172
173#[derive(Clone, Debug, Default, Eq, PartialEq)]
176pub struct Pull {
177 items: Vec<Item>,
178}
179
180impl Pull {
181 #[must_use]
183 pub fn new() -> Self {
184 Self::default()
185 }
186
187 #[must_use]
189 pub fn wildcard(mut self) -> Self {
190 self.items.push(Item::Wildcard);
191 self
192 }
193
194 #[must_use]
196 pub fn db_id(mut self) -> Self {
197 self.items.push(Item::DbId);
198 self
199 }
200
201 #[must_use]
203 pub fn attr(mut self, attr: impl Into<Attr>) -> Self {
204 self.items.push(Item::Attr(attr.into()));
205 self
206 }
207
208 #[must_use]
210 pub fn reverse(mut self, name: impl Into<String>) -> Self {
211 self.items.push(Item::Attr(Attr::new(name).reverse()));
212 self
213 }
214
215 #[must_use]
218 pub fn nested(mut self, attr: impl Into<Attr>, sub: Pull) -> Self {
219 self.items.push(Item::Nested(attr.into(), sub));
220 self
221 }
222
223 #[must_use]
225 pub fn recurse(mut self, attr: impl Into<Attr>, depth: usize) -> Self {
226 self.items.push(Item::Recurse(attr.into(), Some(depth)));
227 self
228 }
229
230 #[must_use]
232 pub fn recurse_unbounded(mut self, attr: impl Into<Attr>) -> Self {
233 self.items.push(Item::Recurse(attr.into(), None));
234 self
235 }
236
237 #[must_use]
239 pub fn spec(mut self, attr: Attr) -> Self {
240 self.items.push(Item::Attr(attr));
241 self
242 }
243
244 #[must_use]
246 pub fn to_edn(&self) -> Edn {
247 Edn::Vector(self.items.iter().map(Item::to_edn).collect())
248 }
249}
250
251#[cfg(test)]
252mod tests {
253 use super::*;
254
255 #[test]
256 fn renders_expected_forms() {
257 let spec = Pull::new()
258 .attr("person/name")
259 .db_id()
260 .wildcard()
261 .reverse("person/manager")
262 .nested("person/friends", Pull::new().attr("person/name"))
263 .recurse("person/reports", 3)
264 .recurse_unbounded("person/mentor")
265 .spec(
266 Attr::new("person/email")
267 .as_key(Edn::keyword("email"))
268 .default("n/a"),
269 );
270 assert_eq!(
271 spec.to_edn().to_string(),
272 "[:person/name :db/id * :person/_manager {:person/friends [:person/name]} \
273 {:person/reports 3} {:person/mentor ...} \
274 [:person/email :as :email :default \"n/a\"]]"
275 );
276 }
277
278 #[test]
279 fn reverse_name_handles_bare_and_namespaced() {
280 assert_eq!(reverse_name("person/manager", true), "person/_manager");
281 assert_eq!(reverse_name("manager", true), "_manager");
282 assert_eq!(reverse_name("person/manager", false), "person/manager");
283 }
284}