Oregami
Repositories/oxedyne/fe2o3

oxedyne/fe2o3/fe2o3_o3db_sync/src/dal/doc.rs

3.2 KiB, 15 runs

created by r1870400018:775, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

1//! This very thin document Data Abstraction Layer:
2//!
3//! - uses path-based heirarchical addressing,
4//! - everything is still ultimately key-value `Dat` pairs.
5//!
6use crate::{
7 base::constant,
8};
9
10use oxedyne_fe2o3_core::{
11 prelude::*,
12};
13
14use oxedyne_fe2o3_jdat::{
15 prelude::*,
16 usr::{
17 UsrKindId,
18 },
19};
20
21
22#[derive(Clone, Debug)]
23pub struct Doc {
24 pub key: DocKey,
25 pub val: Dat,
26}
27
28impl Doc {
29
30 pub fn new_dir<S: Into<String>>(s: S, keys: Vec<String>) -> Outcome<Self> {
31 Ok(Self {
32 key: res!(DocKey::new_dir(s)),
33 val: keys.into(),
34 })
35 }
36
37 pub fn new_doc<S: Into<String>>(s: S, doc: Dat) -> Outcome<Self> {
38 Ok(Self {
39 key: res!(DocKey::new_doc(s)),
40 val: doc.into(),
41 })
42 }
43
44 pub fn into_dats(self) -> (Dat, Dat) {
45 (self.key.into_dat(), self.val)
46 }
47}
48
49#[derive(Clone, Debug)]
50pub enum DocKey {
51 Dir(String),
52 Doc(String),
53}
54
55impl DocKey {
56
57 pub fn new_dir<S: Into<String>>(s: S) -> Outcome<Self> {
58 let mut s = s.into();
59 res!(Self::validate_path(&s));
60 if s.ends_with('/') { s.pop(); }
61 Ok(Self::Dir(s))
62 }
63
64 pub fn new_doc<S: Into<String>>(s: S) -> Outcome<Self> {
65 let mut s = s.into();
66 res!(Self::validate_path(&s));
67 if s.ends_with('/') { s.pop(); }
68 Ok(Self::Doc(s))
69 }
70
71 fn ukind_dir() -> UsrKindId {
72 UsrKindId::new(constant::USER_KIND_DIR_CODE, Some("dir"), Some(Kind::Str))
73 }
74
75 fn ukind_doc() -> UsrKindId {
76 UsrKindId::new(constant::USER_KIND_DOC_CODE, Some("doc"), Some(Kind::Str))
77 }
78
79 pub fn into_dat(self) -> Dat {
80 match self {
81 Self::Dir(key) => {
82 Dat::Usr(Self::ukind_dir(), Some(Box::new(Dat::Str(key))))
83 }
84 Self::Doc(key) => {
85 Dat::Usr(Self::ukind_doc(), Some(Box::new(Dat::Str(key))))
86 }
87 }
88 }
89
90 pub fn parent(&self) -> Option<Self> {
91 match self {
92 Self::Dir(s) | Self::Doc(s) => {
93 match s.rsplit_once('/') {
94 None => None,
95 Some((pre, _post)) => Some(Self::Dir(pre.to_string())),
96 }
97 }
98 }
99 }
100
101 pub fn validate_path(s: &str) -> Outcome<()> {
102 // Length limit.
103 let len = s.chars().count();
104 if len > constant::DOC_PATH_LEN_LIMIT {
105 return Err(err!(
106 "The doc path '{}' of length {} exceeds the limit of {}.",
107 s, len, constant::DOC_PATH_LEN_LIMIT;
108 Invalid, Input, String, TooBig));
109 }
110 // Must start with a '/'.
111 if !s.starts_with('/') {
112 return Err(err!(
113 "The doc path '{}' does not start with a '/'.", s;
114 Invalid, Input, String, Missing));
115 }
116 // No parts with zero length.
117 for (i, part) in s.split('/').enumerate() {
118 if part.chars().count() == 0 && i > 0 {
119 return Err(err!(
120 "Zero length part of path '{}' not allowed in components {:?}",
121 s, s.split('/').collect::<Vec<_>>();
122 Invalid, Input, String, TooSmall));
123 }
124 }
125 Ok(())
126 }
127}