radicle_cob/object/
collaboration.rs

1// Copyright © 2022 The Radicle Link Contributors
2use std::convert::Infallible;
3use std::fmt::Debug;
4
5use git_ext::Oid;
6use nonempty::NonEmpty;
7
8use crate::change::store::{Manifest, Version};
9use crate::{change, Entry, History, ObjectId, TypeName};
10
11pub mod error;
12
13mod create;
14pub use create::{create, Create};
15
16mod get;
17pub use get::get;
18
19pub mod info;
20
21mod list;
22pub use list::list;
23
24mod remove;
25pub use remove::remove;
26
27mod update;
28pub use update::{update, Update, Updated};
29
30/// A collaborative object
31#[derive(Debug, Clone, PartialEq, Eq)]
32pub struct CollaborativeObject<T> {
33    /// The manifest of this object
34    pub manifest: Manifest,
35    /// The materialized object resulting from traversing the history.
36    pub object: T,
37    /// The history DAG.
38    pub history: History,
39    /// The id of the object
40    pub id: ObjectId,
41}
42
43impl<T> CollaborativeObject<T> {
44    pub fn object(&self) -> &T {
45        &self.object
46    }
47
48    pub fn history(&self) -> &History {
49        &self.history
50    }
51
52    pub fn id(&self) -> &ObjectId {
53        &self.id
54    }
55
56    pub fn typename(&self) -> &TypeName {
57        &self.manifest.type_name
58    }
59
60    pub fn manifest(&self) -> &Manifest {
61        &self.manifest
62    }
63}
64
65/// An object that can be built by evaluating a history.
66pub trait Evaluate<R>: Sized + Debug + 'static {
67    type Error: std::error::Error + Send + Sync + 'static;
68
69    /// Initialize the object with the first (root) history entry.
70    fn init(entry: &Entry, store: &R) -> Result<Self, Self::Error>;
71
72    /// Apply a history entry to the evaluated state.
73    fn apply<'a, I: Iterator<Item = (&'a Oid, &'a Entry)>>(
74        &mut self,
75        entry: &Entry,
76        concurrent: I,
77        store: &R,
78    ) -> Result<(), Self::Error>;
79}
80
81impl<R> Evaluate<R> for NonEmpty<Entry> {
82    type Error = Infallible;
83
84    fn init(entry: &Entry, _store: &R) -> Result<Self, Self::Error> {
85        Ok(Self::new(entry.clone()))
86    }
87
88    fn apply<'a, I: Iterator<Item = (&'a Oid, &'a Entry)>>(
89        &mut self,
90        entry: &Entry,
91        _concurrent: I,
92        _store: &R,
93    ) -> Result<(), Self::Error> {
94        self.push(entry.clone());
95
96        Ok(())
97    }
98}
99
100/// Takes a `refname` and performs a best attempt to extract out the
101/// [`TypeName`] and [`ObjectId`] from it.
102///
103/// This assumes that the `refname` is in a
104/// [`git_ext::ref_format::Qualified`] format. If it has any
105/// `refs/namespaces`, they will be stripped to access the underlying
106/// [`git_ext::ref_format::Qualified`] format.
107///
108/// In the [`git_ext::ref_format::Qualified`] format it assumes that the
109/// reference name is of the form:
110///
111///   `refs/<category>/<typename>/<object_id>[/<rest>*]`
112///
113/// Note that their may be more components to the path after the
114/// [`ObjectId`] but they are ignored.
115///
116/// Also note that this will return `None` if:
117///
118///   * The `refname` is not [`git_ext::ref_format::Qualified`]
119///   * The parsing of the [`ObjectId`] fails
120///   * The parsing of the [`TypeName`] fails
121pub fn parse_refstr<R>(name: &R) -> Option<(TypeName, ObjectId)>
122where
123    R: AsRef<git_ext::ref_format::RefStr>,
124{
125    use git_ext::ref_format::Qualified;
126    let name = name.as_ref();
127    let refs_cobs = match name.to_namespaced() {
128        None => Qualified::from_refstr(name)?,
129        Some(ns) => ns.strip_namespace_recursive(),
130    };
131
132    let (_refs, _cobs, typename, mut object_id) = refs_cobs.non_empty_components();
133    let object = object_id
134        .next()
135        .and_then(|oid| oid.parse::<ObjectId>().ok())?;
136    let name = typename.parse::<TypeName>().ok()?;
137    Some((name, object))
138}