lgtm-cli 0.3.1

Run LGTM code reviews with a bundled web UI and gRPC server.
//! Diff transport mapping. The content service has no review persistence dependency.
use super::Api;
use crate::backend::{models as m, proto::lgtm::v1::*};
use connectrpc::{
    ConnectError, Encodable, RequestContext, Response, ServiceRequest, ServiceResult,
};

impl DiffService for Api {
    #[tracing::instrument(name = "rpc_request", skip_all, fields(rpc.method = "lgtm.v1/get_diff"))]
    async fn get_diff<'a>(
        &'a self,
        _ctx: RequestContext,
        request: ServiceRequest<'_, GetDiffRequest>,
    ) -> ServiceResult<impl Encodable<GetDiffResponse> + Send + use<'a>> {
        let request = request.to_owned_message();
        let selected = self
            .diffs
            .get_diff(
                m::ReviewTarget::from_proto(&request.target)?,
                m::ReviewComparison::from_proto(&request.comparison)?,
            )
            .await
            .map_err(ConnectError::from)?;
        Response::ok(GetDiffResponse {
            diff: Some(m::DiffComparison::into_proto(selected)).into(),
            ..Default::default()
        })
    }

    #[tracing::instrument(name = "rpc_request", skip_all, fields(rpc.method = "lgtm.v1/get_files"))]
    async fn get_files<'a>(
        &'a self,
        _ctx: RequestContext,
        request: ServiceRequest<'_, GetFilesRequest>,
    ) -> ServiceResult<impl Encodable<GetFilesResponse> + Send + use<'a>> {
        let request = request.to_owned_message();
        let selected = m::DiffComparison::from_proto(&request.diff)?;
        let files = self
            .diffs
            .get_files(&selected)
            .await
            .map_err(ConnectError::from)?;
        let prefix = format!("{}:", selected.observation_token);
        let offset = if request.page_token.is_empty() {
            0
        } else {
            request
                .page_token
                .strip_prefix(&prefix)
                .and_then(|s| s.parse::<usize>().ok())
                .filter(|offset| *offset <= files.len())
                .ok_or_else(|| {
                    ConnectError::failed_precondition("File page belongs to another observation")
                })?
        };
        let size = if request.page_size == 0 {
            100
        } else {
            request.page_size.min(500)
        } as usize;
        let next = if offset + size < files.len() {
            format!("{prefix}{}", offset + size)
        } else {
            String::new()
        };
        Response::ok(GetFilesResponse {
            files: files
                .into_iter()
                .skip(offset)
                .take(size)
                .map(m::DiffFile::into_proto)
                .collect(),
            next_page_token: next,
            ..Default::default()
        })
    }

    #[tracing::instrument(name = "rpc_request", skip_all, fields(rpc.method = "lgtm.v1/get_file"))]
    async fn get_file<'a>(
        &'a self,
        _ctx: RequestContext,
        request: ServiceRequest<'_, GetFileRequest>,
    ) -> ServiceResult<impl Encodable<GetFileResponse> + Send + use<'a>> {
        let request = request.to_owned_message();
        if request.anchors.len() > 500 {
            return Err(ConnectError::invalid_argument(
                "Selected file exceeds 500 supplied anchors",
            ));
        }
        let selected = m::DiffComparison::from_proto(&request.diff)?;
        let content = self
            .diffs
            .get_file(&selected, &request.path)
            .await
            .map_err(ConnectError::from)?;
        let mut placements = Vec::with_capacity(request.anchors.len());
        let directory_root = match &selected.target {
            m::ReviewTarget::Directory { path } => {
                Some(std::fs::canonicalize(path).map_err(|_| {
                    ConnectError::failed_precondition("Selected directory is unavailable")
                })?)
            }
            _ => None,
        };
        for (index, candidate) in request.anchors.iter().enumerate() {
            let placement = m::SuppliedAnchor::from_proto(candidate)?
                .get_placement(&content, directory_root.as_deref())?;
            placements.push(placement.into_proto(index));
        }
        Response::ok(GetFileResponse {
            file: Some(m::DiffFile::into_proto(content.file)).into(),
            old_side: content.old.map(m::ContentSide::into_proto).into(),
            new_side: content.new.map(m::ContentSide::into_proto).into(),
            placements,
            ..Default::default()
        })
    }
}