git_bug/replica/entity/operation/
operations.rs

1// git-bug-rs - A rust library for interfacing with git-bug repositories
2//
3// Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
4// SPDX-License-Identifier: GPL-3.0-or-later
5//
6// This file is part of git-bug-rs/git-gub.
7//
8// You should have received a copy of the License along with this program.
9// If not, see <https://www.gnu.org/licenses/agpl.txt>.
10
11//! A collection of multiple [`Operations`][`super::Operation`].
12//! This ensures that invalid sequences of [`Operations`][`super::Operation`]
13//! are not only invalid but completely unrepresentable.
14
15use std::iter::{Once, once};
16
17use serde::{Deserialize, Serialize};
18
19use super::{Operation, operation_data::OperationData};
20use crate::replica::entity::Entity;
21
22/// A collection of multiple [`Operations`][`super::Operation`].
23#[derive(Debug, Deserialize, Serialize)]
24pub struct Operations<E: Entity> {
25    #[serde(bound = "Operation<E>: serde::Serialize + serde::de::DeserializeOwned")]
26    root_operation: Operation<E>,
27
28    #[serde(bound = "Vec<Operation<E>>: serde::Serialize + serde::de::DeserializeOwned")]
29    other_operations: Vec<Operation<E>>,
30}
31
32#[allow(missing_docs)]
33pub mod create {
34    use crate::replica::entity::{Entity, operation::Operation};
35
36    #[derive(Debug, thiserror::Error)]
37    pub enum Error<E: Entity> {
38        #[error("Tried to collect an empty vector to an Operations structure")]
39        EmptyOperations,
40
41        #[error(
42            "Tried to use an non root operation ({first}) as root for an Operations structure."
43        )]
44        OperationsFromNonRoot { first: Box<Operation<E>> },
45    }
46}
47
48impl<E: Entity> Operations<E> {
49    /// Create a new [`Operations`] from a simple vector of single Operations.
50    ///
51    /// # Errors
52    /// - If the first operation in `ops` is not actually a root operation (see
53    ///   [`OperationData::is_root`] for how we determine this.)
54    /// - If the `ops` are empty.
55    // We use only expects.
56    #[allow(clippy::missing_panics_doc)]
57    pub fn from_operations(mut ops: Vec<Operation<E>>) -> Result<Self, create::Error<E>> {
58        if ops.is_empty() {
59            return Err(create::Error::EmptyOperations);
60        }
61
62        if !ops
63            .first()
64            .expect("Exists, as the operations are not empty")
65            .data
66            .is_root()
67        {
68            return Err(create::Error::OperationsFromNonRoot {
69                first: Box::new(ops.remove(0)),
70            });
71        }
72
73        Ok(Self {
74            root_operation: ops.remove(0),
75            other_operations: ops,
76        })
77    }
78
79    /// Return the root operation.
80    pub fn root(&self) -> &Operation<E> {
81        &self.root_operation
82    }
83}
84
85/// Treat this collection of operations as a vector.
86impl<T: Entity> Operations<T> {
87    /// Delegate for [`Vec<T>::len`].
88    #[must_use]
89    // Operations will never be empty, thus no is_empty method.
90    #[allow(clippy::len_without_is_empty)]
91    pub fn len(&self) -> usize {
92        self.other_operations.len() + 1
93    }
94
95    /// Delegate for `iter` on the underlying [`Vec`] .
96    #[must_use]
97    pub fn iter(&self) -> <&Self as IntoIterator>::IntoIter {
98        self.into_iter()
99    }
100}
101
102impl<E: Entity> IntoIterator for Operations<E> {
103    type IntoIter =
104        std::iter::Chain<Once<Self::Item>, <Vec<Operation<E>> as IntoIterator>::IntoIter>;
105    type Item = <Vec<Operation<E>> as IntoIterator>::Item;
106
107    fn into_iter(self) -> Self::IntoIter {
108        once(self.root_operation).chain(self.other_operations)
109    }
110}
111impl<'a, E: Entity> IntoIterator for &'a Operations<E> {
112    type IntoIter =
113        std::iter::Chain<Once<Self::Item>, <&'a Vec<Operation<E>> as IntoIterator>::IntoIter>;
114    type Item = <&'a Vec<Operation<E>> as IntoIterator>::Item;
115
116    fn into_iter(self) -> Self::IntoIter {
117        once(&self.root_operation).chain(self.other_operations.iter())
118    }
119}