snix_store/proto/
grpc_pathinfoservice_wrapper.rs

1use crate::nar::{NarCalculationService, RenderError};
2use crate::pathinfoservice::{PathInfo, PathInfoService};
3use crate::proto;
4use futures::{TryStreamExt, stream::BoxStream};
5use snix_castore::proto as castorepb;
6use std::ops::Deref;
7use tonic::{Request, Response, Result, Status, async_trait};
8use tracing::{instrument, warn};
9
10pub struct GRPCPathInfoServiceWrapper<PS, NS> {
11    path_info_service: PS,
12    // FUTUREWORK: allow exposing without allowing listing
13    nar_calculation_service: NS,
14}
15
16impl<PS, NS> GRPCPathInfoServiceWrapper<PS, NS> {
17    pub fn new(path_info_service: PS, nar_calculation_service: NS) -> Self {
18        Self {
19            path_info_service,
20            nar_calculation_service,
21        }
22    }
23}
24
25#[async_trait]
26impl<PS, NS> proto::path_info_service_server::PathInfoService for GRPCPathInfoServiceWrapper<PS, NS>
27where
28    PS: Deref<Target = dyn PathInfoService> + Send + Sync + 'static,
29    NS: NarCalculationService + Send + Sync + 'static,
30{
31    type ListStream = BoxStream<'static, tonic::Result<proto::PathInfo, Status>>;
32
33    #[instrument(skip_all)]
34    async fn get(
35        &self,
36        request: Request<proto::GetPathInfoRequest>,
37    ) -> Result<Response<proto::PathInfo>> {
38        match request.into_inner().by_what {
39            None => Err(Status::unimplemented("by_what needs to be specified")),
40            Some(proto::get_path_info_request::ByWhat::ByOutputHash(output_digest)) => {
41                let digest: [u8; 20] = output_digest
42                    .to_vec()
43                    .try_into()
44                    .map_err(|_e| Status::invalid_argument("invalid output digest length"))?;
45                match self.path_info_service.get(digest).await {
46                    Ok(None) => Err(Status::not_found("PathInfo not found")),
47                    Ok(Some(path_info)) => Ok(Response::new(proto::PathInfo::from(path_info))),
48                    Err(e) => {
49                        warn!(err = %e, "failed to get PathInfo");
50                        Err(e.into())
51                    }
52                }
53            }
54        }
55    }
56
57    #[instrument(skip_all)]
58    async fn put(&self, request: Request<proto::PathInfo>) -> Result<Response<proto::PathInfo>> {
59        let path_info_proto = request.into_inner();
60
61        let path_info = PathInfo::try_from(path_info_proto)
62            .map_err(|e| Status::invalid_argument(format!("Invalid path info: {e}")))?;
63
64        // Store the PathInfo in the client. Clients MUST validate the data
65        // they receive, so we don't validate additionally here.
66        match self.path_info_service.put(path_info).await {
67            Ok(path_info_new) => Ok(Response::new(proto::PathInfo::from(path_info_new))),
68            Err(e) => {
69                warn!(err = %e, "failed to put PathInfo");
70                Err(e.into())
71            }
72        }
73    }
74
75    #[instrument(skip_all)]
76    async fn calculate_nar(
77        &self,
78        request: Request<castorepb::Entry>,
79    ) -> Result<Response<proto::CalculateNarResponse>> {
80        let root_node = request
81            .into_inner()
82            .try_into_anonymous_node()
83            .map_err(|e| {
84                warn!(err = %e, "invalid root node");
85                Status::invalid_argument("invalid root node")
86            })?;
87
88        match self.nar_calculation_service.calculate_nar(&root_node).await {
89            Ok((nar_size, nar_sha256)) => Ok(Response::new(proto::CalculateNarResponse {
90                nar_size,
91                nar_sha256: nar_sha256.to_vec().into(),
92            })),
93            Err(e) => {
94                warn!(err = %e, "error during NAR calculation");
95                Err(e.into())
96            }
97        }
98    }
99
100    #[instrument(skip_all, err)]
101    async fn list(
102        &self,
103        _request: Request<proto::ListPathInfoRequest>,
104    ) -> Result<Response<Self::ListStream>, Status> {
105        let stream = Box::pin(
106            self.path_info_service
107                .list()
108                .map_ok(proto::PathInfo::from)
109                .map_err(|e| Status::internal(e.to_string())),
110        );
111
112        Ok(Response::new(Box::pin(stream)))
113    }
114}
115
116impl From<RenderError> for tonic::Status {
117    fn from(value: RenderError) -> Self {
118        match value {
119            RenderError::BlobNotFound(_, _) => Self::not_found(value.to_string()),
120            RenderError::DirectoryNotFound(_, _) => Self::not_found(value.to_string()),
121            RenderError::NARWriterError(_) => Self::internal(value.to_string()),
122            RenderError::StoreError(_) => Self::internal(value.to_string()),
123            RenderError::UnexpectedBlobMeta(_, _, _, _) => Self::internal(value.to_string()),
124        }
125    }
126}