jazz-rs 0.7.0

A framework for CRDT based, end-to-end enrypted distributed apps
Documentation
use credo::ScopeID;
use credo::{ClaimBody, CredentialSource, Scope};
use jmbl::Input;
use litl::{impl_debug_as_litl, impl_nested_tagged_data_serde, NestedTaggedData};
use ridl::signing::SignerID;
use caro::ObjectID;

use super::Jazz;
use crate::{conventions::INCLUDE_LOG_STR, team::Team, ManagedJMBL};

#[derive(Clone)]
pub struct Doc {
    pub scope: Scope,
    pub managed_jmbl: ManagedJMBL,
}

impl Doc {
    pub fn id(&self) -> DocID {
        DocID(self.scope.id())
    }

    pub async fn get_author_of_log(&self, log_id: ObjectID) -> Option<SignerID> {
        // TODO: make this async like resolve_named_doc ?
        let expected_val = litl::to_val(log_id).unwrap();
        self.scope
            .current_state_as_of(ti64::now())
            .valid_claims
            .iter()
            .find_map(|(_, (claim, _))| match &claim.body {
                ClaimBody::Statement { path, value }
                    if path == INCLUDE_LOG_STR && value == &expected_val =>
                {
                    Some(claim.by)
                }
                _ => None,
            })
    }
}

impl Jazz {
    pub async fn create_document<I: Into<Input>>(
        &self,
        team: &Team,
        input: I,
    ) -> Doc {
        let credential_source = {(*self.0).borrow().main_credential_source.clone_ref()};
        self.create_document_with_credential_source(
            team,
            input,
            credential_source,
        )
        .await
    }

    pub async fn create_document_with_credential_source<I: Into<Input>>(
        &self,
        team: &Team,
        input: I,
        credential_source: Box<dyn CredentialSource>,
    ) -> Doc {
        let credo = self.credo();
        let recipient = team
            .scope
            .credential_for(&ClaimBody::InheritFrom {
                parent: team.scope.id(),
            })
            .await
            .expect("Expected credentials for creating documents");

        let credential_source_id = credential_source.id_hash();

        let doc_scope = credo
            .create_scope(
                recipient,
                vec![],
                Some(team.scope.id()),
                credential_source,
            )
            .await;

        let background = { self.0.borrow().background.clone() };

        let managed_jmbl =
            ManagedJMBL::create(doc_scope.clone(), input, self.content(), credo, background).await;

        let doc = Doc {
            scope: doc_scope,
            managed_jmbl,
        };

        self.0
            .borrow_mut()
            .docs
            .insert((credential_source_id, doc.id()), doc.clone());

        doc
    }

    pub async fn load_document(&self, doc_id: DocID) -> Doc {
        let credential_source = {(*self.0).borrow().main_credential_source.clone_ref()};
        self.load_document_with_credential_source(
            doc_id,
            credential_source,
        )
        .await
    }

    pub async fn load_document_with_credential_source(
        &self,
        doc_id: DocID,
        credential_source: Box<dyn CredentialSource>,
    ) -> Doc {
        let existing = {self
            .0
            .borrow()
            .docs
            .get(&(credential_source.id_hash(), doc_id))
            .cloned()};
        match existing {
            Some(doc) => doc,
            None => {
                let credential_source_id = credential_source.id_hash();
                let doc_scope = self.credo().get_scope(&doc_id.0, credential_source);

                let (managed_jmbl, loaded) = ManagedJMBL::load(
                    doc_scope.clone(),
                    self.content(),
                    self.0.borrow().background.clone(),
                );

                let doc = Doc {
                    scope: doc_scope,
                    managed_jmbl,
                };

                // insert then wait for loaded to avoid race condition between parallel loads
                self.0
                    .borrow_mut()
                    .docs
                    .insert((credential_source_id, doc_id), doc.clone());

                loaded.await.unwrap();

                doc
            }
        }
    }
}

#[derive(Copy, Clone, PartialEq, Eq, Hash)]
pub struct DocID(pub ScopeID);

impl_nested_tagged_data_serde!(DocID);
impl_debug_as_litl!(DocID);

impl NestedTaggedData for DocID {
    const TAG: &'static str = "doc";

    type Inner = ScopeID;

    fn as_inner(&self) -> &Self::Inner {
        &self.0
    }

    fn from_inner(inner: Self::Inner) -> Self
    where
        Self: Sized,
    {
        DocID(inner)
    }
}