collab-common 0.0.7

Code shared by collab's client and server
Documentation
use core::marker::PhantomData;

use serde::{Deserialize, Serialize};

use crate::File;

/// TODO: docs
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Project {
    name: String,
    root: File,
}

impl Project {
    /// TODO: docs
    #[inline(always)]
    pub fn builder() -> ProjectBuilder<WantsRoot> {
        ProjectBuilder::default()
    }

    /// Returns the name of the project.
    ///
    /// This can be set by the host when starting a new collaboration session.
    ///
    /// If the host doesn't explicitly set its value, it will be the name of
    /// the root directory if sharing an entire worktree, or the name of the
    /// file (including its extension) if sharing a single file.
    #[inline(always)]
    pub fn name(&self) -> &str {
        &self.name
    }

    /// Returns the root of the project.
    #[inline(always)]
    pub fn root(&self) -> &File {
        &self.root
    }

    #[inline(always)]
    fn uninit() -> Self {
        Self { name: String::new(), root: File::uninit() }
    }
}

/// TODO: docs
pub struct ProjectBuilder<S> {
    project: Project,
    _state: PhantomData<S>,
}

impl Default for ProjectBuilder<WantsRoot> {
    #[inline(always)]
    fn default() -> Self {
        Self::new(Project::uninit())
    }
}

impl<S> ProjectBuilder<S> {
    #[inline(always)]
    fn change_state<NewState>(&mut self) -> &mut ProjectBuilder<NewState> {
        // SAFETY: this struct is just a newtype around `Project`, so `&mut
        // self` and `&mut Project` have the same layout.
        unsafe { core::mem::transmute(self) }
    }

    #[inline(always)]
    fn new(project: Project) -> Self {
        Self { project, _state: PhantomData }
    }
}

impl ProjectBuilder<WantsRoot> {
    /// TODO: docs
    #[inline(always)]
    pub fn root(&mut self, root: File) -> &mut ProjectBuilder<WithName> {
        self.project.root = root;
        self.change_state::<WithName>()
    }
}

impl ProjectBuilder<WithName> {
    /// TODO: docs
    #[inline(always)]
    pub fn name<S>(&mut self, name: S) -> &mut ProjectBuilder<Done>
    where
        S: Into<String>,
    {
        self.project.name = name.into();
        self.change_state::<Done>()
    }
}

impl<S: CanBuild<Project>> ProjectBuilder<S> {
    /// TODO: docs
    #[inline(always)]
    pub fn build(&mut self) -> Project {
        core::mem::replace(&mut self.project, Project::uninit())
    }
}

/// TODO: docs
pub struct Done;

/// TODO: docs
pub struct WantsRoot;

/// TODO: docs
pub struct WithName;

/// A trait implemented by ..
pub trait CanBuild<Inner>: Sealed {}

impl CanBuild<Project> for WithName {}

impl CanBuild<Project> for Done {}

impl Sealed for WithName {}

impl Sealed for Done {}

use sealed::Sealed;

mod sealed {
    pub trait Sealed {}
}

#[cfg(test)]
mod test {
    use super::*;
    use crate::{FileIdGenerator, PeerId};

    // foo
    // |- a.txt
    // |- b.txt
    #[test]
    fn project_single_dir_two_children() {
        let ids = FileIdGenerator::new();

        let mut builder = Project::builder();

        let peer_id = PeerId::new(1);

        let root = File::build_directory()
            .file_id(ids.next())
            .name("foo")
            .child(
                File::build_document()
                    .file_id(ids.next())
                    .name("a.txt")
                    .peer_id(peer_id)
                    .text("")
                    .build(),
            )
            .child(
                File::build_document()
                    .file_id(ids.next())
                    .name("b.txt")
                    .peer_id(peer_id)
                    .text("")
                    .build(),
            )
            .build();

        let _project: Project = builder.root(root).name("foo").build();
    }
}