1use std::fmt;
59use std::sync::Arc;
60
61use serde::{Deserialize, Serialize};
62
63use crate::filter_ir::{AuthScope, FilterBuilder, FilterIR};
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().namespace(ns.as_str()).build(),
233 Self::Multiple(nss) => {
234 use crate::filter_ir::{FilterAtom, FilterValue};
235 FilterIR::from_atom(FilterAtom::in_set(
236 "namespace",
237 nss.iter()
238 .map(|ns| FilterValue::String(ns.as_str().to_string()))
239 .collect(),
240 ))
241 }
242 }
243 }
244}
245
246impl fmt::Display for NamespaceScope {
247 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
248 match self {
249 Self::Single(ns) => write!(f, "{}", ns),
250 Self::Multiple(nss) => {
251 let names: Vec<_> = nss.iter().map(|ns| ns.as_str()).collect();
252 write!(f, "[{}]", names.join(", "))
253 }
254 }
255 }
256}
257
258#[derive(Debug, Clone, thiserror::Error)]
260pub enum ScopeError {
261 #[error("namespace not allowed: {0}")]
262 NamespaceNotAllowed(Namespace),
263
264 #[error("auth scope expired")]
265 AuthExpired,
266
267 #[error("insufficient capabilities for this operation")]
268 InsufficientCapabilities,
269}
270
271#[derive(Debug, Clone)]
280pub struct ScopedQuery<Q> {
281 scope: NamespaceScope,
283
284 query: Q,
286
287 filters: FilterIR,
289}
290
291impl<Q> ScopedQuery<Q> {
292 pub fn new(scope: NamespaceScope, query: Q) -> Self {
296 Self {
297 scope,
298 query,
299 filters: FilterIR::all(),
300 }
301 }
302
303 pub fn in_namespace(namespace: Namespace, query: Q) -> Self {
305 Self::new(NamespaceScope::Single(namespace), query)
306 }
307
308 pub fn with_filters(mut self, filters: FilterIR) -> Self {
310 self.filters = filters;
311 self
312 }
313
314 pub fn scope(&self) -> &NamespaceScope {
316 &self.scope
317 }
318
319 pub fn query(&self) -> &Q {
321 &self.query
322 }
323
324 pub fn filters(&self) -> &FilterIR {
326 &self.filters
327 }
328
329 pub fn effective_filter(&self) -> FilterIR {
333 self.scope.to_filter_ir().and(self.filters.clone())
334 }
335
336 pub fn validate(&self, auth: &AuthScope) -> Result<(), ScopeError> {
338 if auth.is_expired() {
340 return Err(ScopeError::AuthExpired);
341 }
342
343 self.scope.validate_against(auth)?;
345
346 Ok(())
347 }
348
349 pub fn into_query(self) -> Q {
351 self.query
352 }
353}
354
355#[derive(Debug, Clone)]
367pub struct QueryRequest<Q> {
368 query: ScopedQuery<Q>,
370
371 auth: Arc<AuthScope>,
373}
374
375impl<Q> QueryRequest<Q> {
376 pub fn new(query: ScopedQuery<Q>, auth: Arc<AuthScope>) -> Result<Self, ScopeError> {
381 query.validate(&auth)?;
382 Ok(Self { query, auth })
383 }
384
385 pub fn query(&self) -> &ScopedQuery<Q> {
387 &self.query
388 }
389
390 pub fn auth(&self) -> &AuthScope {
392 &self.auth
393 }
394
395 pub fn effective_filter(&self) -> FilterIR {
402 self.auth.to_filter_ir().and(self.query.effective_filter())
403 }
404
405 pub fn namespace_scope(&self) -> &NamespaceScope {
407 self.query.scope()
408 }
409}
410
411pub fn ns(name: &str) -> Result<Namespace, NamespaceError> {
417 Namespace::new(name)
418}
419
420pub fn scope(name: &str) -> Result<NamespaceScope, NamespaceError> {
422 Ok(NamespaceScope::Single(Namespace::new(name)?))
423}
424
425#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
447pub struct DatabaseId {
448 pub namespace: String,
450 pub name: String,
452}
453
454impl DatabaseId {
455 pub const MAX_LENGTH: usize = 256;
457
458 pub fn new(
460 namespace: impl Into<String>,
461 name: impl Into<String>,
462 ) -> 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(
549 &mut self,
550 namespace: &str,
551 database: &str,
552 ) -> Result<(), NamespaceError> {
553 Namespace::validate_name(database)?;
554 let dbs = self.databases.entry(namespace.to_string()).or_default();
555 dbs.insert(database.to_string());
556 self.tables
557 .entry((namespace.to_string(), database.to_string()))
558 .or_default();
559 Ok(())
560 }
561
562 pub fn create_table(
564 &mut self,
565 namespace: &str,
566 database: &str,
567 table: &str,
568 ) -> Result<(), NamespaceError> {
569 Namespace::validate_name(table)?;
570 let dbs = self.databases.entry(namespace.to_string()).or_default();
572 dbs.insert(database.to_string());
573 let tables = self
574 .tables
575 .entry((namespace.to_string(), database.to_string()))
576 .or_default();
577 tables.insert(table.to_string());
578 Ok(())
579 }
580
581 pub fn list_databases(&self, namespace: &str) -> Vec<&str> {
583 self.databases
584 .get(namespace)
585 .map(|dbs| dbs.iter().map(|s| s.as_str()).collect())
586 .unwrap_or_default()
587 }
588
589 pub fn list_tables(&self, namespace: &str, database: &str) -> Vec<&str> {
591 self.tables
592 .get(&(namespace.to_string(), database.to_string()))
593 .map(|tables| tables.iter().map(|s| s.as_str()).collect())
594 .unwrap_or_default()
595 }
596
597 pub fn namespace_exists(&self, namespace: &str) -> bool {
599 self.databases.contains_key(namespace)
600 }
601
602 pub fn database_exists(&self, namespace: &str, database: &str) -> bool {
604 self.databases
605 .get(namespace)
606 .map(|dbs| dbs.contains(database))
607 .unwrap_or(false)
608 }
609
610 pub fn table_exists(&self, namespace: &str, database: &str, table: &str) -> bool {
612 self.tables
613 .get(&(namespace.to_string(), database.to_string()))
614 .map(|tables| tables.contains(table))
615 .unwrap_or(false)
616 }
617
618 pub fn drop_database(&mut self, namespace: &str, database: &str) -> bool {
620 self.tables
621 .remove(&(namespace.to_string(), database.to_string()));
622 self.databases
623 .get_mut(namespace)
624 .map(|dbs| dbs.remove(database))
625 .unwrap_or(false)
626 }
627
628 pub fn drop_table(&mut self, namespace: &str, database: &str, table: &str) -> bool {
630 self.tables
631 .get_mut(&(namespace.to_string(), database.to_string()))
632 .map(|tables| tables.remove(table))
633 .unwrap_or(false)
634 }
635
636 pub fn drop_namespace(&mut self, namespace: &str) -> bool {
638 if !self.databases.contains_key(namespace) {
639 return false;
640 }
641 let db_names: Vec<String> = self
643 .databases
644 .get(namespace)
645 .map(|dbs| dbs.iter().cloned().collect())
646 .unwrap_or_default();
647 for db in &db_names {
648 self.tables.remove(&(namespace.to_string(), db.clone()));
649 }
650 self.databases.remove(namespace);
651 true
652 }
653
654 pub fn resolve_table(&self, qualified: &QualifiedTable) -> bool {
656 self.table_exists(&qualified.namespace, &qualified.database, &qualified.table)
657 }
658}
659
660#[cfg(test)]
665mod tests {
666 use super::*;
667
668 #[test]
669 fn test_namespace_validation() {
670 assert!(Namespace::new("production").is_ok());
672 assert!(Namespace::new("my_namespace").is_ok());
673 assert!(Namespace::new("project-123").is_ok());
674 assert!(Namespace::new("v1.0.0").is_ok());
675
676 assert!(Namespace::new("").is_err()); assert!(Namespace::new("-starts-with-dash").is_err());
679 assert!(Namespace::new(".starts-with-dot").is_err());
680 assert!(Namespace::new("has spaces").is_err());
681 assert!(Namespace::new("has@symbol").is_err());
682 }
683
684 #[test]
685 fn test_namespace_scope_single() {
686 let ns = Namespace::new("production").unwrap();
687 let scope = NamespaceScope::single(ns.clone());
688
689 assert!(scope.contains(&ns));
690 assert!(!scope.contains(&Namespace::new("staging").unwrap()));
691 }
692
693 #[test]
694 fn test_namespace_scope_multiple() {
695 let ns1 = Namespace::new("prod").unwrap();
696 let ns2 = Namespace::new("staging").unwrap();
697 let scope = NamespaceScope::multiple(vec![ns1.clone(), ns2.clone()]).unwrap();
698
699 assert!(scope.contains(&ns1));
700 assert!(scope.contains(&ns2));
701 assert!(!scope.contains(&Namespace::new("dev").unwrap()));
702 }
703
704 #[test]
705 fn test_scope_to_filter_ir() {
706 let scope = NamespaceScope::single(Namespace::new("production").unwrap());
707 let filter = scope.to_filter_ir();
708
709 assert!(filter.constrains_field("namespace"));
710 assert_eq!(filter.clauses.len(), 1);
711 }
712
713 #[test]
714 fn test_scoped_query_effective_filter() {
715 let ns = Namespace::new("production").unwrap();
716 let user_filter = FilterBuilder::new().eq("source", "documents").build();
717
718 let query: ScopedQuery<()> = ScopedQuery::in_namespace(ns, ()).with_filters(user_filter);
719
720 let effective = query.effective_filter();
721 assert!(effective.constrains_field("namespace"));
722 assert!(effective.constrains_field("source"));
723 }
724
725 #[test]
726 fn test_query_request_validation() {
727 let ns = Namespace::new("production").unwrap();
728 let query: ScopedQuery<()> = ScopedQuery::in_namespace(ns, ());
729
730 let auth = Arc::new(AuthScope::for_namespace("production"));
732 assert!(QueryRequest::new(query.clone(), auth).is_ok());
733
734 let auth2 = Arc::new(AuthScope::for_namespace("staging"));
736 assert!(QueryRequest::new(query, auth2).is_err());
737 }
738
739 #[test]
740 fn test_query_request_effective_filter() {
741 let ns = Namespace::new("production").unwrap();
742 let query: ScopedQuery<()> = ScopedQuery::in_namespace(ns, ())
743 .with_filters(FilterBuilder::new().eq("type", "article").build());
744
745 let auth = Arc::new(AuthScope::for_namespace("production").with_tenant("acme"));
746
747 let request = QueryRequest::new(query, auth).unwrap();
748 let effective = request.effective_filter();
749
750 assert!(effective.constrains_field("namespace"));
752 assert!(effective.constrains_field("tenant_id"));
753 assert!(effective.constrains_field("type"));
754 }
755
756 #[test]
759 fn test_database_id_creation() {
760 let db = DatabaseId::new("production", "app").unwrap();
761 assert_eq!(db.namespace, "production");
762 assert_eq!(db.name, "app");
763 assert_eq!(db.qualified_name(), "production/app");
764 }
765
766 #[test]
767 fn test_qualified_table() {
768 let qt = QualifiedTable::new("production", "app", "users");
769 assert_eq!(qt.qualified_name(), "production/app/users");
770 assert_eq!(qt.storage_prefix(), "production:app:users");
771 }
772
773 #[test]
774 fn test_namespace_registry_basic() {
775 let mut reg = NamespaceRegistry::new();
776 reg.create_namespace("prod").unwrap();
777 assert!(reg.namespace_exists("prod"));
778 assert!(!reg.namespace_exists("staging"));
779 }
780
781 #[test]
782 fn test_namespace_registry_databases() {
783 let mut reg = NamespaceRegistry::new();
784 reg.create_namespace("prod").unwrap();
785 reg.create_database("prod", "app").unwrap();
786 reg.create_database("prod", "analytics").unwrap();
787 assert!(reg.database_exists("prod", "app"));
788 assert!(reg.database_exists("prod", "analytics"));
789 assert!(!reg.database_exists("prod", "logs"));
790 let dbs = reg.list_databases("prod");
791 assert_eq!(dbs.len(), 2);
792 }
793
794 #[test]
795 fn test_namespace_registry_tables() {
796 let mut reg = NamespaceRegistry::new();
797 reg.create_table("prod", "app", "users").unwrap();
798 reg.create_table("prod", "app", "posts").unwrap();
799 assert!(reg.table_exists("prod", "app", "users"));
800 assert!(reg.table_exists("prod", "app", "posts"));
801 assert!(!reg.table_exists("prod", "app", "comments"));
802 assert!(reg.database_exists("prod", "app"));
804 }
805
806 #[test]
807 fn test_namespace_registry_drop() {
808 let mut reg = NamespaceRegistry::new();
809 reg.create_table("prod", "app", "users").unwrap();
810 reg.create_table("prod", "app", "posts").unwrap();
811 reg.create_table("prod", "analytics", "events").unwrap();
812
813 assert!(reg.drop_table("prod", "app", "users"));
815 assert!(!reg.table_exists("prod", "app", "users"));
816 assert!(reg.table_exists("prod", "app", "posts"));
817
818 assert!(reg.drop_database("prod", "app"));
820 assert!(!reg.database_exists("prod", "app"));
821 assert!(!reg.table_exists("prod", "app", "posts"));
822
823 assert!(reg.table_exists("prod", "analytics", "events"));
825
826 assert!(reg.drop_namespace("prod"));
828 assert!(!reg.namespace_exists("prod"));
829 assert!(!reg.table_exists("prod", "analytics", "events"));
830 }
831
832 #[test]
833 fn test_qualified_table_resolve() {
834 let mut reg = NamespaceRegistry::new();
835 reg.create_table("prod", "app", "users").unwrap();
836 let qt = QualifiedTable::new("prod", "app", "users");
837 assert!(reg.resolve_table(&qt));
838 let missing = QualifiedTable::new("prod", "app", "absent");
839 assert!(!reg.resolve_table(&missing));
840 }
841}