tc-server 0.1.0

TinyChain's cluster server logic, including peer discovery and replication
Documentation
use std::ops::Deref;

use log::debug;
use safecast::{TryCastFrom, TryCastInto};

use tc_error::*;
use tc_scalar::value::{Link, Version as VersionNumber};
use tc_scalar::{OpRef, Scalar, TCRef};
use tc_state::object::InstanceClass;
use tc_transact::public::{GetHandler, Handler, PostHandler, PutHandler, Route};
use tc_transact::{Transaction, TxnId};
use tc_value::Value;
use tcgeneric::{Id, Map, PathSegment, TCPath};

use crate::cluster::dir::DirItem;
use crate::cluster::Library;
use crate::{State, Txn};

struct LibraryHandler<'a> {
    library: &'a Library,
}

impl<'a> Handler<'a, State> for LibraryHandler<'a> {
    fn get<'b>(self: Box<Self>) -> Option<GetHandler<'a, 'b, Txn, State>>
    where
        'b: 'a,
    {
        Some(Box::new(|txn, key| {
            Box::pin(async move {
                if key.is_some() {
                    let number: VersionNumber =
                        key.try_cast_into(|v| TCError::unexpected(v, "a semantic version number"))?;

                    let version = self.library.get_version(*txn.id(), &number).await?;

                    Ok(State::Scalar(version.clone().into()))
                } else {
                    let version_numbers = self.library.list_versions(*txn.id()).await?;
                    let version_numbers = version_numbers.into_iter().map(Id::from).collect();
                    Ok(Value::Tuple(version_numbers).into())
                }
            })
        }))
    }

    fn put<'b>(self: Box<Self>) -> Option<PutHandler<'a, 'b, Txn, State>>
    where
        'b: 'a,
    {
        Some(Box::new(|txn, key, value| {
            Box::pin(async move {
                let number: VersionNumber =
                    key.try_cast_into(|v| TCError::unexpected(v, "a semantic version number"))?;

                let (_link, schema) = expect_version(value)?;

                let mut version = Map::<Scalar>::new();

                fn is_dep(scalar: &Scalar) -> bool {
                    if let Scalar::Ref(tc_ref) = scalar {
                        if let TCRef::Op(op_ref) = &**tc_ref {
                            if let OpRef::Post(_) = op_ref {
                                return true;
                            }
                        }
                    }

                    false
                }

                for (name, state) in schema.into_iter() {
                    let scalar = Scalar::try_from(state)?;

                    if is_dep(&scalar) {
                        InstanceClass::try_cast_from(scalar, |s| {
                            TCError::unexpected(s, "a class definition")
                        })?;
                    } else {
                        version.insert(name, scalar);
                    }
                }

                self.library.create_version(txn, number, version).await?;

                Ok(())
            })
        }))
    }
}

impl<'a> From<&'a Library> for LibraryHandler<'a> {
    fn from(library: &'a Library) -> Self {
        Self { library }
    }
}

struct VersionHandler<'a> {
    library: &'a Library,
    version: &'a PathSegment,
}

impl<'a> VersionHandler<'a> {
    fn new(library: &'a Library, version: &'a PathSegment) -> Self {
        Self { library, version }
    }
}

impl<'a> Handler<'a, State> for VersionHandler<'a> {
    fn get<'b>(self: Box<Self>) -> Option<GetHandler<'a, 'b, Txn, State>>
    where
        'b: 'a,
    {
        Some(Box::new(|txn, key| {
            Box::pin(async move {
                let number: VersionNumber = self.version.as_str().parse()?;

                let version = self.library.get_version(*txn.id(), &number).await?;

                if key.is_none() {
                    Ok(State::Scalar(version.clone().into()))
                } else {
                    let attr_name: Id =
                        key.try_cast_into(|v| TCError::unexpected(v, "a library attribute name"))?;

                    version
                        .get(&attr_name)
                        .cloned()
                        .map(State::Scalar)
                        .ok_or_else(|| not_found!("library attribute {attr_name}"))
                }
            })
        }))
    }
}

struct LibraryAttrHandler<'a> {
    library: &'a Library,
    version: &'a Id,
    path: &'a [PathSegment],
}

impl<'a> LibraryAttrHandler<'a> {
    fn new(
        library: &'a Library,
        version: &'a Id,
        path: &'a [PathSegment],
    ) -> LibraryAttrHandler<'a> {
        Self {
            library,
            version,
            path,
        }
    }

    async fn get_version(&self, txn_id: TxnId) -> TCResult<impl Deref<Target = Map<Scalar>>> {
        let number: VersionNumber = self.version.as_str().parse()?;
        self.library.get_version(txn_id, &number).await
    }
}

impl<'a> Handler<'a, State> for LibraryAttrHandler<'a> {
    fn get<'b>(self: Box<Self>) -> Option<GetHandler<'a, 'b, Txn, State>>
    where
        'b: 'a,
    {
        Some(Box::new(move |txn, key| {
            Box::pin(async move {
                let version = self.get_version(*txn.id()).await?;

                debug!(
                    "execute GET {}: {} from library version",
                    TCPath::from(self.path),
                    key
                );

                tc_transact::public::Public::get(&*version, txn, &self.path, key).await
            })
        }))
    }

    fn post<'b>(self: Box<Self>) -> Option<PostHandler<'a, 'b, Txn, State>>
    where
        'b: 'a,
    {
        Some(Box::new(move |txn, params| {
            Box::pin(async move {
                let version = self.get_version(*txn.id()).await?;
                tc_transact::public::Public::post(&*version, txn, &self.path, params).await
            })
        }))
    }
}

impl Route<State> for Library {
    fn route<'a>(&'a self, path: &'a [PathSegment]) -> Option<Box<dyn Handler<'a, State> + 'a>> {
        debug!("Library::route {}", TCPath::from(path));

        if path.is_empty() {
            Some(Box::new(LibraryHandler::from(self)))
        } else if path.len() == 1 {
            Some(Box::new(VersionHandler::new(self, &path[0])))
        } else {
            Some(Box::new(LibraryAttrHandler::new(
                self,
                &path[0],
                &path[1..],
            )))
        }
    }
}

#[inline]
fn expect_version(version: State) -> TCResult<(Link, Map<Scalar>)> {
    InstanceClass::try_cast_from(version, |v| TCError::unexpected(v, "a Class"))
        .map(|class| class.into_inner())
}