1use std::{
2 collections::{BTreeMap, BTreeSet},
3 sync::Arc,
4};
5
6use crate::{
7 Cx, Error, Expr, Result, Symbol, Value,
8 capability::registry_catalog_read_capability,
9 id::CORE_TABLE_CLASS_ID,
10 object::{ClassRef, Object},
11 table::{Dir, Table},
12};
13
14use super::{CatalogSnapshot, CatalogSnapshotRow};
15
16#[derive(Clone, Debug)]
24pub struct CatalogDirView {
25 snapshot: CatalogSnapshot,
26 path: Vec<Symbol>,
27}
28
29impl CatalogDirView {
30 pub fn new(snapshot: CatalogSnapshot) -> Self {
32 Self {
33 snapshot,
34 path: Vec::new(),
35 }
36 }
37
38 fn at_path(snapshot: CatalogSnapshot, path: Vec<Symbol>) -> Self {
39 Self { snapshot, path }
40 }
41
42 pub fn snapshot(&self) -> &CatalogSnapshot {
44 &self.snapshot
45 }
46
47 fn table_for_path(&self, path: &[Symbol]) -> Option<Symbol> {
48 table_symbol_for_path(path).filter(|table| self.snapshot.tables.contains_key(table))
49 }
50
51 fn has_namespace_for_path(&self, path: &[Symbol]) -> bool {
52 self.snapshot
53 .tables
54 .keys()
55 .map(table_path)
56 .any(|table_path| path_is_prefix(path, &table_path) && path.len() < table_path.len())
57 }
58
59 fn child_path(&self, name: &Symbol) -> Vec<Symbol> {
60 self.path.iter().cloned().chain(symbol_path(name)).collect()
61 }
62
63 fn child_names(&self) -> Vec<Symbol> {
64 let names = self
65 .snapshot
66 .tables
67 .keys()
68 .map(table_path)
69 .filter_map(|table_path| {
70 path_is_prefix(&self.path, &table_path)
71 .then(|| table_path.get(self.path.len()).cloned())
72 .flatten()
73 })
74 .collect::<BTreeSet<_>>();
75 names.into_iter().collect()
76 }
77
78 fn child_value(&self, cx: &mut Cx, name: Symbol) -> Result<Option<Value>> {
79 let path = self.child_path(&name);
80 if let Some(table) = self.table_for_path(&path) {
81 return cx
82 .factory()
83 .opaque(Arc::new(CatalogTableView {
84 snapshot: self.snapshot.clone(),
85 table,
86 }))
87 .map(Some);
88 }
89 if self.has_namespace_for_path(&path) {
90 return cx
91 .factory()
92 .opaque(Arc::new(Self::at_path(self.snapshot.clone(), path)))
93 .map(Some);
94 }
95 Ok(None)
96 }
97
98 fn read_only_error(&self) -> Error {
99 Error::CatalogReadOnly {
100 table: Symbol::new("registry-catalog-view"),
101 }
102 }
103}
104
105impl Object for CatalogDirView {
106 fn display(&self, _cx: &mut Cx) -> Result<String> {
107 Ok(format!("catalog-dir[{}]", path_display(&self.path)))
108 }
109
110 fn as_any(&self) -> &dyn std::any::Any {
111 self
112 }
113}
114
115impl crate::ObjectCompat for CatalogDirView {
116 fn class(&self, cx: &mut Cx) -> Result<ClassRef> {
117 let symbol = Symbol::qualified("core", "Table");
118 if let Some(value) = cx.registry().class_by_symbol(&symbol) {
119 return Ok(value.clone());
120 }
121 cx.factory().class_stub(CORE_TABLE_CLASS_ID, symbol)
122 }
123
124 fn as_expr(&self, cx: &mut Cx) -> Result<Expr> {
125 self.as_table_expr(cx)
126 }
127
128 fn truth(&self, cx: &mut Cx) -> Result<bool> {
129 Ok(!self.is_empty(cx)?)
130 }
131
132 fn as_table_impl(&self) -> Option<&dyn Table> {
133 Some(self)
134 }
135
136 fn as_dir(&self) -> Option<&dyn Dir> {
137 Some(self)
138 }
139}
140
141impl Table for CatalogDirView {
142 fn backend_symbol(&self) -> Symbol {
143 Symbol::qualified("catalog", "dir-view")
144 }
145
146 fn get(&self, cx: &mut Cx, key: Symbol) -> Result<Value> {
147 cx.require(®istry_catalog_read_capability())?;
148 match self.child_value(cx, key)? {
149 Some(value) => Ok(value),
150 None => cx.factory().nil(),
151 }
152 }
153
154 fn set(&self, _cx: &mut Cx, _key: Symbol, _value: Value) -> Result<()> {
155 Err(self.read_only_error())
156 }
157
158 fn has(&self, cx: &mut Cx, key: Symbol) -> Result<bool> {
159 cx.require(®istry_catalog_read_capability())?;
160 let path = self.child_path(&key);
161 Ok(self.table_for_path(&path).is_some() || self.has_namespace_for_path(&path))
162 }
163
164 fn del(&self, _cx: &mut Cx, _key: Symbol) -> Result<Value> {
165 Err(self.read_only_error())
166 }
167
168 fn keys(&self, cx: &mut Cx) -> Result<Vec<Symbol>> {
169 cx.require(®istry_catalog_read_capability())?;
170 Ok(self.child_names())
171 }
172
173 fn entries(&self, cx: &mut Cx) -> Result<Vec<(Symbol, Value)>> {
174 cx.require(®istry_catalog_read_capability())?;
175 self.child_names()
176 .into_iter()
177 .filter_map(|key| {
178 self.child_value(cx, key.clone())
179 .transpose()
180 .map(|value| value.map(|value| (key, value)))
181 })
182 .collect()
183 }
184
185 fn len(&self, cx: &mut Cx) -> Result<usize> {
186 cx.require(®istry_catalog_read_capability())?;
187 Ok(self.child_names().len())
188 }
189
190 fn clear(&self, _cx: &mut Cx) -> Result<()> {
191 Err(self.read_only_error())
192 }
193}
194
195impl Dir for CatalogDirView {
196 fn mkdir(&self, _cx: &mut Cx, _name: Symbol) -> Result<Value> {
197 Err(self.read_only_error())
198 }
199
200 fn opendir(&self, cx: &mut Cx, name: Symbol) -> Result<Option<Value>> {
201 cx.require(®istry_catalog_read_capability())?;
202 self.child_value(cx, name)
203 }
204
205 fn rmdir(&self, _cx: &mut Cx, _name: Symbol) -> Result<Value> {
206 Err(self.read_only_error())
207 }
208
209 fn is_dir(&self, cx: &mut Cx, name: Symbol) -> Result<bool> {
210 cx.require(®istry_catalog_read_capability())?;
211 self.has(cx, name)
212 }
213}
214
215#[derive(Clone, Debug)]
222pub struct CatalogTableView {
223 snapshot: CatalogSnapshot,
224 table: Symbol,
225}
226
227impl CatalogTableView {
228 pub fn new(snapshot: CatalogSnapshot, table: Symbol) -> Self {
230 Self { snapshot, table }
231 }
232
233 pub fn table(&self) -> &Symbol {
235 &self.table
236 }
237
238 fn rows(&self) -> impl Iterator<Item = (&Symbol, &CatalogSnapshotRow)> {
239 self.snapshot
240 .rows(&self.table)
241 .into_iter()
242 .flat_map(BTreeMap::iter)
243 }
244}
245
246impl Object for CatalogTableView {
247 fn display(&self, _cx: &mut Cx) -> Result<String> {
248 Ok(format!("catalog-table[{}]", self.table))
249 }
250
251 fn as_any(&self) -> &dyn std::any::Any {
252 self
253 }
254}
255
256impl crate::ObjectCompat for CatalogTableView {
257 fn class(&self, cx: &mut Cx) -> Result<ClassRef> {
258 let symbol = Symbol::qualified("core", "Table");
259 if let Some(value) = cx.registry().class_by_symbol(&symbol) {
260 return Ok(value.clone());
261 }
262 cx.factory().class_stub(CORE_TABLE_CLASS_ID, symbol)
263 }
264
265 fn as_expr(&self, cx: &mut Cx) -> Result<Expr> {
266 self.as_table_expr(cx)
267 }
268
269 fn truth(&self, cx: &mut Cx) -> Result<bool> {
270 Ok(!self.is_empty(cx)?)
271 }
272
273 fn as_table_impl(&self) -> Option<&dyn Table> {
274 Some(self)
275 }
276}
277
278impl Table for CatalogTableView {
279 fn backend_symbol(&self) -> Symbol {
280 Symbol::qualified("catalog", "table-view")
281 }
282
283 fn get(&self, cx: &mut Cx, key: Symbol) -> Result<Value> {
284 cx.require(®istry_catalog_read_capability())?;
285 match self
286 .snapshot
287 .rows(&self.table)
288 .and_then(|rows| rows.get(&key))
289 {
290 Some(row) => row_value(cx, row),
291 None => cx.factory().nil(),
292 }
293 }
294
295 fn set(&self, _cx: &mut Cx, _key: Symbol, _value: Value) -> Result<()> {
296 Err(Error::CatalogReadOnly {
297 table: self.table.clone(),
298 })
299 }
300
301 fn has(&self, cx: &mut Cx, key: Symbol) -> Result<bool> {
302 cx.require(®istry_catalog_read_capability())?;
303 Ok(self
304 .snapshot
305 .rows(&self.table)
306 .is_some_and(|rows| rows.contains_key(&key)))
307 }
308
309 fn del(&self, _cx: &mut Cx, _key: Symbol) -> Result<Value> {
310 Err(Error::CatalogReadOnly {
311 table: self.table.clone(),
312 })
313 }
314
315 fn keys(&self, cx: &mut Cx) -> Result<Vec<Symbol>> {
316 cx.require(®istry_catalog_read_capability())?;
317 Ok(self.rows().map(|(key, _)| key.clone()).collect())
318 }
319
320 fn entries(&self, cx: &mut Cx) -> Result<Vec<(Symbol, Value)>> {
321 cx.require(®istry_catalog_read_capability())?;
322 self.rows()
323 .map(|(key, row)| row_value(cx, row).map(|value| (key.clone(), value)))
324 .collect()
325 }
326
327 fn len(&self, cx: &mut Cx) -> Result<usize> {
328 cx.require(®istry_catalog_read_capability())?;
329 Ok(self.rows().count())
330 }
331
332 fn clear(&self, _cx: &mut Cx) -> Result<()> {
333 Err(Error::CatalogReadOnly {
334 table: self.table.clone(),
335 })
336 }
337}
338
339pub fn registry_catalog_view(cx: &mut Cx) -> Result<Value> {
342 cx.require(®istry_catalog_read_capability())?;
343 let snapshot = cx.registry().catalog_snapshot();
344 cx.factory().opaque(Arc::new(CatalogDirView::new(snapshot)))
345}
346
347fn row_value(cx: &mut Cx, row: &CatalogSnapshotRow) -> Result<Value> {
348 let entries = row
349 .data
350 .iter()
351 .map(|(field, value)| {
352 cx.factory()
353 .expr(value.clone())
354 .map(|value| (field.clone(), value))
355 })
356 .collect::<Result<Vec<_>>>()?;
357 cx.factory().table(entries)
358}
359
360fn symbol_path(symbol: &Symbol) -> impl Iterator<Item = Symbol> {
361 symbol
362 .as_qualified_str()
363 .split('/')
364 .map(|part| Symbol::new(part.to_owned()))
365 .collect::<Vec<_>>()
366 .into_iter()
367}
368
369fn table_path(table: &Symbol) -> Vec<Symbol> {
370 symbol_path(table).collect()
371}
372
373fn table_symbol_for_path(path: &[Symbol]) -> Option<Symbol> {
374 match path {
375 [name] => Some(Symbol::new(name.as_qualified_str())),
376 [namespace, name] => Some(Symbol::qualified(
377 namespace.as_qualified_str(),
378 name.as_qualified_str(),
379 )),
380 _ => None,
381 }
382}
383
384fn path_is_prefix(prefix: &[Symbol], path: &[Symbol]) -> bool {
385 prefix.len() <= path.len() && prefix.iter().zip(path).all(|(left, right)| left == right)
386}
387
388fn path_display(path: &[Symbol]) -> String {
389 if path.is_empty() {
390 "/".to_owned()
391 } else {
392 path.iter()
393 .map(Symbol::as_qualified_str)
394 .collect::<Vec<_>>()
395 .join("/")
396 }
397}