Skip to main content

snix_castore/directoryservice/
simple_putter.rs

1use super::Directory;
2use super::DirectoryPutter;
3use super::DirectoryService;
4use crate::B3Digest;
5use crate::directoryservice::directory_graph::DirectoryGraphBuilder;
6use crate::directoryservice::order_validator::{self, LeavesToRoot};
7use tonic::async_trait;
8use tracing::instrument;
9use tracing::warn;
10
11/// This is an implementation of DirectoryPutter that simply
12/// inserts individual Directory messages one by one, on close, after
13/// they successfully validated.
14pub struct SimplePutter<'a, DS> {
15    directory_service: &'a DS,
16
17    builder: Option<DirectoryGraphBuilder<LeavesToRoot>>,
18}
19
20impl<'a, DS> SimplePutter<'a, DS>
21where
22    DS: DirectoryService,
23{
24    pub fn new(directory_service: &'a DS) -> Self {
25        Self {
26            directory_service,
27            builder: Some(DirectoryGraphBuilder::<LeavesToRoot>::new()),
28        }
29    }
30}
31
32#[async_trait]
33impl<DS: DirectoryService + 'static> DirectoryPutter for SimplePutter<'_, DS> {
34    #[instrument(level = "trace", skip_all, fields(directory.digest=%directory.digest()), err)]
35    async fn put(&mut self, directory: Directory) -> Result<(), super::Error> {
36        let builder = self.builder.as_mut().ok_or_else(|| Error::AlreadyClosed)?;
37
38        builder
39            .try_insert(directory)
40            .map_err(Error::OrderingError)?;
41
42        Ok(())
43    }
44
45    #[instrument(level = "trace", skip_all, ret, err)]
46    async fn close(&mut self) -> Result<B3Digest, super::Error> {
47        let builder = self.builder.take().ok_or_else(|| Error::AlreadyClosed)?;
48
49        // Retrieve the validated directories.
50        let directory_graph = builder.build().map_err(Error::OrderingError)?;
51        let root_digest = directory_graph.root().digest();
52
53        for directory in directory_graph.drain_leaves_to_root() {
54            let exp_digest = directory.digest();
55            let actual_digest = self.directory_service.put(directory).await?;
56
57            // ensure the digest the backend told us matches our expectations.
58            if exp_digest != actual_digest {
59                warn!(directory.digest_expected=%exp_digest, directory.digest_actual=%actual_digest, "unexpected digest");
60                Err(Error::UnexpectedDigest {
61                    expected: exp_digest,
62                    actual: actual_digest,
63                })?;
64            }
65        }
66
67        Ok(root_digest)
68    }
69}
70
71#[derive(thiserror::Error, Debug)]
72pub enum Error {
73    #[error("DirectoryGraphBuilder already closed")]
74    AlreadyClosed,
75    #[error("got unexpected digest from backend, expected {expected}, actual {actual}")]
76    UnexpectedDigest {
77        expected: B3Digest,
78        actual: B3Digest,
79    },
80    #[error("failure during graph validation")]
81    OrderingError(#[from] order_validator::OrderingError),
82}
83
84impl From<Error> for super::Error {
85    fn from(value: Error) -> Self {
86        Self(Box::new(value))
87    }
88}