1use std::fmt;
59use std::sync::Arc;
60
61use serde::{Deserialize, Serialize};
62
63use crate::filter_ir::{AuthScope, FilterIR, FilterBuilder};
64
65#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
74pub struct Namespace(String);
75
76impl Namespace {
77 pub const MAX_LENGTH: usize = 256;
79
80 pub fn new(name: impl Into<String>) -> Result<Self, NamespaceError> {
88 let name = name.into();
89 Self::validate(&name)?;
90 Ok(Self(name))
91 }
92
93 #[allow(dead_code)]
98 pub(crate) fn new_unchecked(name: impl Into<String>) -> Self {
99 Self(name.into())
100 }
101
102 fn validate(name: &str) -> Result<(), NamespaceError> {
104 Self::validate_name(name)
105 }
106
107 pub fn validate_name(name: &str) -> Result<(), NamespaceError> {
109 if name.is_empty() {
110 return Err(NamespaceError::Empty);
111 }
112
113 if name.len() > Self::MAX_LENGTH {
114 return Err(NamespaceError::TooLong {
115 length: name.len(),
116 max: Self::MAX_LENGTH,
117 });
118 }
119
120 let first = name.chars().next().unwrap();
122 if first == '.' || first == '-' {
123 return Err(NamespaceError::InvalidStart(first));
124 }
125
126 for (i, ch) in name.chars().enumerate() {
128 if !ch.is_alphanumeric() && ch != '_' && ch != '-' && ch != '.' {
129 return Err(NamespaceError::InvalidChar { ch, position: i });
130 }
131 }
132
133 Ok(())
134 }
135
136 pub fn as_str(&self) -> &str {
138 &self.0
139 }
140
141 pub fn into_string(self) -> String {
143 self.0
144 }
145}
146
147impl fmt::Display for Namespace {
148 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
149 write!(f, "{}", self.0)
150 }
151}
152
153impl AsRef<str> for Namespace {
154 fn as_ref(&self) -> &str {
155 &self.0
156 }
157}
158
159#[derive(Debug, Clone, thiserror::Error)]
161pub enum NamespaceError {
162 #[error("namespace cannot be empty")]
163 Empty,
164
165 #[error("namespace too long: {length} > {max}")]
166 TooLong { length: usize, max: usize },
167
168 #[error("namespace cannot start with '{0}'")]
169 InvalidStart(char),
170
171 #[error("invalid character '{ch}' at position {position}")]
172 InvalidChar { ch: char, position: usize },
173}
174
175#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
181pub enum NamespaceScope {
182 Single(Namespace),
184
185 Multiple(Vec<Namespace>),
187}
188
189impl NamespaceScope {
190 pub fn single(ns: Namespace) -> Self {
192 Self::Single(ns)
193 }
194
195 pub fn multiple(namespaces: Vec<Namespace>) -> Result<Self, NamespaceError> {
197 if namespaces.is_empty() {
198 return Err(NamespaceError::Empty);
199 }
200 Ok(Self::Multiple(namespaces))
201 }
202
203 pub fn namespaces(&self) -> Vec<&Namespace> {
205 match self {
206 Self::Single(ns) => vec![ns],
207 Self::Multiple(nss) => nss.iter().collect(),
208 }
209 }
210
211 pub fn contains(&self, ns: &Namespace) -> bool {
213 match self {
214 Self::Single(single) => single == ns,
215 Self::Multiple(multiple) => multiple.contains(ns),
216 }
217 }
218
219 pub fn validate_against(&self, auth: &AuthScope) -> Result<(), ScopeError> {
221 for ns in self.namespaces() {
222 if !auth.is_namespace_allowed(ns.as_str()) {
223 return Err(ScopeError::NamespaceNotAllowed(ns.clone()));
224 }
225 }
226 Ok(())
227 }
228
229 pub fn to_filter_ir(&self) -> FilterIR {
231 match self {
232 Self::Single(ns) => FilterBuilder::new()
233 .namespace(ns.as_str())
234 .build(),
235 Self::Multiple(nss) => {
236 use crate::filter_ir::{FilterAtom, FilterValue};
237 FilterIR::from_atom(FilterAtom::in_set(
238 "namespace",
239 nss.iter()
240 .map(|ns| FilterValue::String(ns.as_str().to_string()))
241 .collect(),
242 ))
243 }
244 }
245 }
246}
247
248impl fmt::Display for NamespaceScope {
249 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
250 match self {
251 Self::Single(ns) => write!(f, "{}", ns),
252 Self::Multiple(nss) => {
253 let names: Vec<_> = nss.iter().map(|ns| ns.as_str()).collect();
254 write!(f, "[{}]", names.join(", "))
255 }
256 }
257 }
258}
259
260#[derive(Debug, Clone, thiserror::Error)]
262pub enum ScopeError {
263 #[error("namespace not allowed: {0}")]
264 NamespaceNotAllowed(Namespace),
265
266 #[error("auth scope expired")]
267 AuthExpired,
268
269 #[error("insufficient capabilities for this operation")]
270 InsufficientCapabilities,
271}
272
273#[derive(Debug, Clone)]
282pub struct ScopedQuery<Q> {
283 scope: NamespaceScope,
285
286 query: Q,
288
289 filters: FilterIR,
291}
292
293impl<Q> ScopedQuery<Q> {
294 pub fn new(scope: NamespaceScope, query: Q) -> Self {
298 Self {
299 scope,
300 query,
301 filters: FilterIR::all(),
302 }
303 }
304
305 pub fn in_namespace(namespace: Namespace, query: Q) -> Self {
307 Self::new(NamespaceScope::Single(namespace), query)
308 }
309
310 pub fn with_filters(mut self, filters: FilterIR) -> Self {
312 self.filters = filters;
313 self
314 }
315
316 pub fn scope(&self) -> &NamespaceScope {
318 &self.scope
319 }
320
321 pub fn query(&self) -> &Q {
323 &self.query
324 }
325
326 pub fn filters(&self) -> &FilterIR {
328 &self.filters
329 }
330
331 pub fn effective_filter(&self) -> FilterIR {
335 self.scope.to_filter_ir().and(self.filters.clone())
336 }
337
338 pub fn validate(&self, auth: &AuthScope) -> Result<(), ScopeError> {
340 if auth.is_expired() {
342 return Err(ScopeError::AuthExpired);
343 }
344
345 self.scope.validate_against(auth)?;
347
348 Ok(())
349 }
350
351 pub fn into_query(self) -> Q {
353 self.query
354 }
355}
356
357#[derive(Debug, Clone)]
369pub struct QueryRequest<Q> {
370 query: ScopedQuery<Q>,
372
373 auth: Arc<AuthScope>,
375}
376
377impl<Q> QueryRequest<Q> {
378 pub fn new(query: ScopedQuery<Q>, auth: Arc<AuthScope>) -> Result<Self, ScopeError> {
383 query.validate(&auth)?;
384 Ok(Self { query, auth })
385 }
386
387 pub fn query(&self) -> &ScopedQuery<Q> {
389 &self.query
390 }
391
392 pub fn auth(&self) -> &AuthScope {
394 &self.auth
395 }
396
397 pub fn effective_filter(&self) -> FilterIR {
404 self.auth.to_filter_ir()
405 .and(self.query.effective_filter())
406 }
407
408 pub fn namespace_scope(&self) -> &NamespaceScope {
410 self.query.scope()
411 }
412}
413
414pub fn ns(name: &str) -> Result<Namespace, NamespaceError> {
420 Namespace::new(name)
421}
422
423pub fn scope(name: &str) -> Result<NamespaceScope, NamespaceError> {
425 Ok(NamespaceScope::Single(Namespace::new(name)?))
426}
427
428#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
450pub struct DatabaseId {
451 pub namespace: String,
453 pub name: String,
455}
456
457impl DatabaseId {
458 pub const MAX_LENGTH: usize = 256;
460
461 pub fn new(namespace: impl Into<String>, name: impl Into<String>) -> Result<Self, NamespaceError> {
463 let namespace = namespace.into();
464 let name = name.into();
465 Namespace::validate_name(&name)?;
466 Ok(Self { namespace, name })
467 }
468
469 pub fn qualified_name(&self) -> String {
471 format!("{}/{}", self.namespace, self.name)
472 }
473}
474
475impl fmt::Display for DatabaseId {
476 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
477 write!(f, "{}/{}", self.namespace, self.name)
478 }
479}
480
481#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
485pub struct QualifiedTable {
486 pub namespace: String,
487 pub database: String,
488 pub table: String,
489}
490
491impl QualifiedTable {
492 pub fn new(
494 namespace: impl Into<String>,
495 database: impl Into<String>,
496 table: impl Into<String>,
497 ) -> Self {
498 Self {
499 namespace: namespace.into(),
500 database: database.into(),
501 table: table.into(),
502 }
503 }
504
505 pub fn qualified_name(&self) -> String {
507 format!("{}/{}/{}", self.namespace, self.database, self.table)
508 }
509
510 pub fn storage_prefix(&self) -> String {
513 format!("{}:{}:{}", self.namespace, self.database, self.table)
514 }
515}
516
517impl fmt::Display for QualifiedTable {
518 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
519 write!(f, "{}/{}/{}", self.namespace, self.database, self.table)
520 }
521}
522
523#[derive(Debug, Clone, Default)]
527pub struct NamespaceRegistry {
528 databases: std::collections::HashMap<String, std::collections::HashSet<String>>,
530 tables: std::collections::HashMap<(String, String), std::collections::HashSet<String>>,
532}
533
534impl NamespaceRegistry {
535 pub fn new() -> Self {
537 Self::default()
538 }
539
540 pub fn create_namespace(&mut self, namespace: &str) -> Result<(), NamespaceError> {
542 Namespace::validate_name(namespace)?;
543 self.databases.entry(namespace.to_string()).or_default();
544 Ok(())
545 }
546
547 pub fn create_database(&mut self, namespace: &str, database: &str) -> Result<(), NamespaceError> {
549 Namespace::validate_name(database)?;
550 let dbs = self.databases.entry(namespace.to_string()).or_default();
551 dbs.insert(database.to_string());
552 self.tables.entry((namespace.to_string(), database.to_string())).or_default();
553 Ok(())
554 }
555
556 pub fn create_table(&mut self, namespace: &str, database: &str, table: &str) -> Result<(), NamespaceError> {
558 Namespace::validate_name(table)?;
559 let dbs = self.databases.entry(namespace.to_string()).or_default();
561 dbs.insert(database.to_string());
562 let tables = self.tables.entry((namespace.to_string(), database.to_string())).or_default();
563 tables.insert(table.to_string());
564 Ok(())
565 }
566
567 pub fn list_databases(&self, namespace: &str) -> Vec<&str> {
569 self.databases.get(namespace)
570 .map(|dbs| dbs.iter().map(|s| s.as_str()).collect())
571 .unwrap_or_default()
572 }
573
574 pub fn list_tables(&self, namespace: &str, database: &str) -> Vec<&str> {
576 self.tables.get(&(namespace.to_string(), database.to_string()))
577 .map(|tables| tables.iter().map(|s| s.as_str()).collect())
578 .unwrap_or_default()
579 }
580
581 pub fn namespace_exists(&self, namespace: &str) -> bool {
583 self.databases.contains_key(namespace)
584 }
585
586 pub fn database_exists(&self, namespace: &str, database: &str) -> bool {
588 self.databases.get(namespace)
589 .map(|dbs| dbs.contains(database))
590 .unwrap_or(false)
591 }
592
593 pub fn table_exists(&self, namespace: &str, database: &str, table: &str) -> bool {
595 self.tables.get(&(namespace.to_string(), database.to_string()))
596 .map(|tables| tables.contains(table))
597 .unwrap_or(false)
598 }
599
600 pub fn drop_database(&mut self, namespace: &str, database: &str) -> bool {
602 self.tables.remove(&(namespace.to_string(), database.to_string()));
603 self.databases.get_mut(namespace)
604 .map(|dbs| dbs.remove(database))
605 .unwrap_or(false)
606 }
607
608 pub fn drop_table(&mut self, namespace: &str, database: &str, table: &str) -> bool {
610 self.tables.get_mut(&(namespace.to_string(), database.to_string()))
611 .map(|tables| tables.remove(table))
612 .unwrap_or(false)
613 }
614
615 pub fn drop_namespace(&mut self, namespace: &str) -> bool {
617 if !self.databases.contains_key(namespace) {
618 return false;
619 }
620 let db_names: Vec<String> = self.databases.get(namespace)
622 .map(|dbs| dbs.iter().cloned().collect())
623 .unwrap_or_default();
624 for db in &db_names {
625 self.tables.remove(&(namespace.to_string(), db.clone()));
626 }
627 self.databases.remove(namespace);
628 true
629 }
630
631 pub fn resolve_table(&self, qualified: &QualifiedTable) -> bool {
633 self.table_exists(&qualified.namespace, &qualified.database, &qualified.table)
634 }
635}
636
637#[cfg(test)]
642mod tests {
643 use super::*;
644
645 #[test]
646 fn test_namespace_validation() {
647 assert!(Namespace::new("production").is_ok());
649 assert!(Namespace::new("my_namespace").is_ok());
650 assert!(Namespace::new("project-123").is_ok());
651 assert!(Namespace::new("v1.0.0").is_ok());
652
653 assert!(Namespace::new("").is_err()); assert!(Namespace::new("-starts-with-dash").is_err());
656 assert!(Namespace::new(".starts-with-dot").is_err());
657 assert!(Namespace::new("has spaces").is_err());
658 assert!(Namespace::new("has@symbol").is_err());
659 }
660
661 #[test]
662 fn test_namespace_scope_single() {
663 let ns = Namespace::new("production").unwrap();
664 let scope = NamespaceScope::single(ns.clone());
665
666 assert!(scope.contains(&ns));
667 assert!(!scope.contains(&Namespace::new("staging").unwrap()));
668 }
669
670 #[test]
671 fn test_namespace_scope_multiple() {
672 let ns1 = Namespace::new("prod").unwrap();
673 let ns2 = Namespace::new("staging").unwrap();
674 let scope = NamespaceScope::multiple(vec![ns1.clone(), ns2.clone()]).unwrap();
675
676 assert!(scope.contains(&ns1));
677 assert!(scope.contains(&ns2));
678 assert!(!scope.contains(&Namespace::new("dev").unwrap()));
679 }
680
681 #[test]
682 fn test_scope_to_filter_ir() {
683 let scope = NamespaceScope::single(Namespace::new("production").unwrap());
684 let filter = scope.to_filter_ir();
685
686 assert!(filter.constrains_field("namespace"));
687 assert_eq!(filter.clauses.len(), 1);
688 }
689
690 #[test]
691 fn test_scoped_query_effective_filter() {
692 let ns = Namespace::new("production").unwrap();
693 let user_filter = FilterBuilder::new()
694 .eq("source", "documents")
695 .build();
696
697 let query: ScopedQuery<()> = ScopedQuery::in_namespace(ns, ())
698 .with_filters(user_filter);
699
700 let effective = query.effective_filter();
701 assert!(effective.constrains_field("namespace"));
702 assert!(effective.constrains_field("source"));
703 }
704
705 #[test]
706 fn test_query_request_validation() {
707 let ns = Namespace::new("production").unwrap();
708 let query: ScopedQuery<()> = ScopedQuery::in_namespace(ns, ());
709
710 let auth = Arc::new(AuthScope::for_namespace("production"));
712 assert!(QueryRequest::new(query.clone(), auth).is_ok());
713
714 let auth2 = Arc::new(AuthScope::for_namespace("staging"));
716 assert!(QueryRequest::new(query, auth2).is_err());
717 }
718
719 #[test]
720 fn test_query_request_effective_filter() {
721 let ns = Namespace::new("production").unwrap();
722 let query: ScopedQuery<()> = ScopedQuery::in_namespace(ns, ())
723 .with_filters(FilterBuilder::new().eq("type", "article").build());
724
725 let auth = Arc::new(
726 AuthScope::for_namespace("production")
727 .with_tenant("acme")
728 );
729
730 let request = QueryRequest::new(query, auth).unwrap();
731 let effective = request.effective_filter();
732
733 assert!(effective.constrains_field("namespace"));
735 assert!(effective.constrains_field("tenant_id"));
736 assert!(effective.constrains_field("type"));
737 }
738
739 #[test]
742 fn test_database_id_creation() {
743 let db = DatabaseId::new("production", "app").unwrap();
744 assert_eq!(db.namespace, "production");
745 assert_eq!(db.name, "app");
746 assert_eq!(db.qualified_name(), "production/app");
747 }
748
749 #[test]
750 fn test_qualified_table() {
751 let qt = QualifiedTable::new("production", "app", "users");
752 assert_eq!(qt.qualified_name(), "production/app/users");
753 assert_eq!(qt.storage_prefix(), "production:app:users");
754 }
755
756 #[test]
757 fn test_namespace_registry_basic() {
758 let mut reg = NamespaceRegistry::new();
759 reg.create_namespace("prod").unwrap();
760 assert!(reg.namespace_exists("prod"));
761 assert!(!reg.namespace_exists("staging"));
762 }
763
764 #[test]
765 fn test_namespace_registry_databases() {
766 let mut reg = NamespaceRegistry::new();
767 reg.create_namespace("prod").unwrap();
768 reg.create_database("prod", "app").unwrap();
769 reg.create_database("prod", "analytics").unwrap();
770 assert!(reg.database_exists("prod", "app"));
771 assert!(reg.database_exists("prod", "analytics"));
772 assert!(!reg.database_exists("prod", "logs"));
773 let dbs = reg.list_databases("prod");
774 assert_eq!(dbs.len(), 2);
775 }
776
777 #[test]
778 fn test_namespace_registry_tables() {
779 let mut reg = NamespaceRegistry::new();
780 reg.create_table("prod", "app", "users").unwrap();
781 reg.create_table("prod", "app", "posts").unwrap();
782 assert!(reg.table_exists("prod", "app", "users"));
783 assert!(reg.table_exists("prod", "app", "posts"));
784 assert!(!reg.table_exists("prod", "app", "comments"));
785 assert!(reg.database_exists("prod", "app"));
787 }
788
789 #[test]
790 fn test_namespace_registry_drop() {
791 let mut reg = NamespaceRegistry::new();
792 reg.create_table("prod", "app", "users").unwrap();
793 reg.create_table("prod", "app", "posts").unwrap();
794 reg.create_table("prod", "analytics", "events").unwrap();
795
796 assert!(reg.drop_table("prod", "app", "users"));
798 assert!(!reg.table_exists("prod", "app", "users"));
799 assert!(reg.table_exists("prod", "app", "posts"));
800
801 assert!(reg.drop_database("prod", "app"));
803 assert!(!reg.database_exists("prod", "app"));
804 assert!(!reg.table_exists("prod", "app", "posts"));
805
806 assert!(reg.table_exists("prod", "analytics", "events"));
808
809 assert!(reg.drop_namespace("prod"));
811 assert!(!reg.namespace_exists("prod"));
812 assert!(!reg.table_exists("prod", "analytics", "events"));
813 }
814
815 #[test]
816 fn test_qualified_table_resolve() {
817 let mut reg = NamespaceRegistry::new();
818 reg.create_table("prod", "app", "users").unwrap();
819 let qt = QualifiedTable::new("prod", "app", "users");
820 assert!(reg.resolve_table(&qt));
821 let missing = QualifiedTable::new("prod", "app", "absent");
822 assert!(!reg.resolve_table(&missing));
823 }
824}