tc_state/
public.rs

1use std::marker::PhantomData;
2
3use futures::TryFutureExt;
4use log::debug;
5use safecast::TryCastInto;
6
7use tc_error::*;
8#[cfg(any(feature = "btree", feature = "table"))]
9use tc_transact::fs;
10use tc_transact::hash::AsyncHash;
11use tc_transact::public::helpers::{AttributeHandler, EchoHandler, SelfHandler};
12use tc_transact::public::{GetHandler, Handler, PostHandler, Route};
13use tc_transact::{Gateway, Transaction};
14use tc_value::{Link, Number, Value};
15use tcgeneric::{label, Id, Instance, Label, Map, NativeClass, PathSegment, TCPath};
16
17#[cfg(any(feature = "btree", feature = "table"))]
18use crate::collection::{BTreeFile, BTreeSchema};
19#[cfg(feature = "table")]
20use crate::collection::{TableFile, TableSchema};
21use crate::object::{InstanceClass, Object};
22#[cfg(any(feature = "btree", feature = "table"))]
23use crate::Collection;
24use crate::{CacheBlock, State, StateType};
25
26pub const PREFIX: Label = label("state");
27
28struct ClassHandler {
29    class: StateType,
30}
31
32impl<'a, Txn> Handler<'a, State<Txn>> for ClassHandler
33where
34    Txn: Transaction<CacheBlock> + Gateway<State<Txn>>,
35{
36    fn get<'b>(self: Box<Self>) -> Option<GetHandler<'a, 'b, Txn, State<Txn>>>
37    where
38        'b: 'a,
39    {
40        Some(Box::new(|_txn, _key| {
41            Box::pin(async move { Ok(Link::from(self.class.path()).into()) })
42        }))
43    }
44
45    fn post<'b>(self: Box<Self>) -> Option<PostHandler<'a, 'b, Txn, State<Txn>>>
46    where
47        'b: 'a,
48    {
49        Some(Box::new(|_txn, params| {
50            Box::pin(async move {
51                let mut proto = Map::new();
52                for (id, member) in params.into_iter() {
53                    let member = member.try_cast_into(|s| {
54                        TCError::unexpected(s, "an attribute in an public prototype")
55                    })?;
56
57                    proto.insert(id, member);
58                }
59
60                let class = InstanceClass::extend(self.class.path().clone(), proto);
61                Ok(Object::Class(class).into())
62            })
63        }))
64    }
65}
66
67struct HashHandler<Txn> {
68    state: State<Txn>,
69}
70
71impl<'a, Txn> Handler<'a, State<Txn>> for HashHandler<Txn>
72where
73    Txn: Transaction<CacheBlock> + Gateway<State<Txn>>,
74{
75    fn get<'b>(self: Box<Self>) -> Option<GetHandler<'a, 'b, Txn, State<Txn>>>
76    where
77        'b: 'a,
78    {
79        Some(Box::new(|txn, key| {
80            Box::pin(async move {
81                key.expect_none()?;
82
83                self.state
84                    .hash(*txn.id())
85                    .map_ok(Id::from)
86                    .map_ok(Value::from)
87                    .map_ok(State::from)
88                    .await
89            })
90        }))
91    }
92}
93
94impl<Txn> From<State<Txn>> for HashHandler<Txn> {
95    fn from(state: State<Txn>) -> HashHandler<Txn> {
96        Self { state }
97    }
98}
99
100impl From<StateType> for ClassHandler {
101    fn from(class: StateType) -> Self {
102        Self { class }
103    }
104}
105
106impl<Txn> Route<State<Txn>> for StateType
107where
108    Txn: Transaction<CacheBlock> + Gateway<State<Txn>>,
109{
110    fn route<'a>(
111        &'a self,
112        path: &'a [PathSegment],
113    ) -> Option<Box<dyn Handler<'a, State<Txn>> + 'a>> {
114        let child_handler = match self {
115            #[cfg(feature = "chain")]
116            Self::Chain(ct) => ct.route(path),
117            #[cfg(feature = "collection")]
118            Self::Collection(ct) => ct.route(path),
119            Self::Object(ot) => ot.route(path),
120            Self::Scalar(st) => st.route(path),
121            _ => None,
122        };
123
124        if child_handler.is_some() {
125            return child_handler;
126        }
127
128        if path.is_empty() {
129            Some(Box::new(ClassHandler::from(*self)))
130        } else {
131            None
132        }
133    }
134}
135
136impl<Txn> Route<State<Txn>> for State<Txn>
137where
138    Txn: Transaction<CacheBlock> + Gateway<Self>,
139{
140    fn route<'a>(
141        &'a self,
142        path: &'a [PathSegment],
143    ) -> Option<Box<dyn Handler<'a, State<Txn>> + 'a>> {
144        debug!(
145            "instance of {:?} route {}",
146            self.class(),
147            TCPath::from(path)
148        );
149
150        if let Some(handler) = match self {
151            #[cfg(feature = "chain")]
152            Self::Chain(chain) => chain.route(path),
153            Self::Closure(closure) if path.is_empty() => {
154                let handler: Box<dyn Handler<'a, State<Txn>> + 'a> = Box::new(closure.clone());
155                Some(handler)
156            }
157            #[cfg(feature = "collection")]
158            Self::Collection(collection) => collection.route(path),
159            Self::Map(map) => map.route(path),
160            Self::Object(object) => object.route(path),
161            Self::Scalar(scalar) => scalar.route(path),
162            Self::Tuple(tuple) => tuple.route(path),
163            _ => None,
164        } {
165            return Some(handler);
166        }
167
168        if path.is_empty() {
169            Some(Box::new(SelfHandler::from(self)))
170        } else if path.len() == 1 {
171            match path[0].as_str() {
172                "class" => Some(Box::new(ClassHandler::from(self.class()))),
173                "hash" => Some(Box::new(HashHandler::from(self.clone()))),
174                "is_none" => Some(Box::new(AttributeHandler::from(Number::Bool(
175                    self.is_none().into(),
176                )))),
177                _ => None,
178            }
179        } else {
180            None
181        }
182    }
183}
184
185#[derive(Copy, Clone)]
186pub struct Static<Txn> {
187    phantom: PhantomData<Txn>,
188}
189
190impl<Txn> Default for Static<Txn> {
191    fn default() -> Self {
192        Self {
193            phantom: PhantomData,
194        }
195    }
196}
197
198#[cfg(all(feature = "collection", not(feature = "btree"), not(feature = "table")))]
199impl<Txn> Route<State<Txn>> for Static<Txn>
200where
201    Txn: Transaction<CacheBlock> + Gateway<State<Txn>>,
202{
203    fn route<'a>(
204        &'a self,
205        path: &'a [PathSegment],
206    ) -> Option<Box<dyn Handler<'a, State<Txn>> + 'a>> {
207        if path.is_empty() {
208            return Some(Box::new(EchoHandler));
209        }
210
211        match path[0].as_str() {
212            "collection" => tc_collection::public::Static.route(&path[1..]),
213            "scalar" => tc_scalar::public::Static.route(&path[1..]),
214            "map" => tc_transact::public::generic::MapStatic.route(&path[1..]),
215            "tuple" => tc_transact::public::generic::TupleStatic.route(&path[1..]),
216            _ => None,
217        }
218    }
219}
220
221#[cfg(all(feature = "btree", not(feature = "table")))]
222impl<Txn> Route<State<Txn>> for Static<Txn>
223where
224    Txn: Transaction<CacheBlock> + Gateway<State<Txn>>,
225    BTreeFile<Txn>: fs::Persist<CacheBlock, Schema = BTreeSchema, Txn = Txn>,
226    Collection<Txn>: From<BTreeFile<Txn>>,
227{
228    fn route<'a>(
229        &'a self,
230        path: &'a [PathSegment],
231    ) -> Option<Box<dyn Handler<'a, State<Txn>> + 'a>> {
232        if path.is_empty() {
233            return Some(Box::new(EchoHandler));
234        }
235
236        match path[0].as_str() {
237            "collection" => tc_collection::public::Static.route(&path[1..]),
238            "scalar" => tc_scalar::public::Static.route(&path[1..]),
239            "map" => tc_transact::public::generic::MapStatic.route(&path[1..]),
240            "tuple" => tc_transact::public::generic::TupleStatic.route(&path[1..]),
241            _ => None,
242        }
243    }
244}
245
246#[cfg(feature = "table")]
247impl<Txn> Route<State<Txn>> for Static<Txn>
248where
249    Txn: Transaction<CacheBlock> + Gateway<State<Txn>>,
250    BTreeFile<Txn>: fs::Persist<CacheBlock, Schema = BTreeSchema, Txn = Txn>,
251    TableFile<Txn>: fs::Persist<CacheBlock, Schema = TableSchema, Txn = Txn>,
252    Collection<Txn>: From<BTreeFile<Txn>>,
253{
254    fn route<'a>(
255        &'a self,
256        path: &'a [PathSegment],
257    ) -> Option<Box<dyn Handler<'a, State<Txn>> + 'a>> {
258        if path.is_empty() {
259            return Some(Box::new(EchoHandler));
260        }
261
262        match path[0].as_str() {
263            "collection" => tc_collection::public::Static.route(&path[1..]),
264            "scalar" => tc_scalar::public::Static.route(&path[1..]),
265            "map" => tc_transact::public::generic::MapStatic.route(&path[1..]),
266            "tuple" => tc_transact::public::generic::TupleStatic.route(&path[1..]),
267            _ => None,
268        }
269    }
270}
271
272#[cfg(not(feature = "collection"))]
273impl<Txn> Route<State<Txn>> for Static<Txn>
274where
275    Txn: Transaction<CacheBlock> + Gateway<State<Txn>>,
276{
277    fn route<'a>(
278        &'a self,
279        path: &'a [PathSegment],
280    ) -> Option<Box<dyn Handler<'a, State<Txn>> + 'a>> {
281        if path.is_empty() {
282            return Some(Box::new(EchoHandler));
283        }
284
285        match path[0].as_str() {
286            "scalar" => tc_scalar::public::Static.route(&path[1..]),
287            "map" => tc_transact::public::generic::MapStatic.route(&path[1..]),
288            "tuple" => tc_transact::public::generic::TupleStatic.route(&path[1..]),
289            _ => None,
290        }
291    }
292}