auv-api-server 0.0.30

Protocol adapters for AUV daemon control and capability APIs
//! Device service adapter.

use std::sync::Arc;

use auv_api_proto::auv::api::daemon::v1 as proto;
use auv_api_proto::auv::api::daemon::v1::device_service_server::DeviceService;
use tonic::{Request, Response, Status};

use crate::authentication;
use crate::control::Control;
use crate::protocol::domain;
use crate::protocol::grpc::status::map_control_error;

#[derive(Clone)]
pub(crate) struct DeviceServiceGrpc {
  daemon: Arc<dyn Control>,
}

impl DeviceServiceGrpc {
  pub(crate) fn new(daemon: Arc<dyn Control>) -> Self {
    Self { daemon }
  }
}

#[tonic::async_trait]
impl DeviceService for DeviceServiceGrpc {
  async fn list_devices(&self, _request: Request<proto::ListDevicesRequest>) -> Result<Response<proto::ListDevicesResponse>, Status> {
    let devices = self.daemon.list_devices().map_err(map_control_error)?.into_iter().map(domain::device).collect();
    Ok(Response::new(proto::ListDevicesResponse { devices }))
  }

  async fn get_device(&self, request: Request<proto::GetDeviceRequest>) -> Result<Response<proto::GetDeviceResponse>, Status> {
    let device_id = request
      .into_inner()
      .device
      .map(|device| device.device_id)
      .filter(|device_id| !device_id.is_empty())
      .ok_or_else(|| Status::invalid_argument("device is required"))?;
    let device = self
      .daemon
      .get_device(&device_id)
      .map_err(map_control_error)?
      .ok_or_else(|| Status::not_found(format!("unknown Device: {device_id}")))?;
    Ok(Response::new(proto::GetDeviceResponse {
      device: Some(domain::device(device)),
    }))
  }

  async fn list_user_sessions(
    &self,
    request: Request<proto::ListUserSessionsRequest>,
  ) -> Result<Response<proto::ListUserSessionsResponse>, Status> {
    let caller = authentication::device_entry_caller(&request)?;
    let result = match self.daemon.list_user_sessions(caller) {
      Ok(sessions) => proto::list_user_sessions_response::Result::List(proto::UserSessionList {
        sessions: sessions.into_iter().map(domain::user_session).collect(),
      }),
      Err(reason) => proto::list_user_sessions_response::Result::Error(proto::DeviceEntryError {
        reason: domain::device_entry_error_reason(reason) as i32,
      }),
    };

    Ok(Response::new(proto::ListUserSessionsResponse {
      result: Some(result),
    }))
  }

  async fn get_user_session(
    &self,
    request: Request<proto::GetUserSessionRequest>,
  ) -> Result<Response<proto::GetUserSessionResponse>, Status> {
    let caller = authentication::device_entry_caller(&request)?;
    let session_selector = &request.get_ref().session_selector;

    if session_selector.trim().is_empty() {
      return Err(Status::invalid_argument("session_selector is required"));
    }

    let result = match self.daemon.get_user_session(caller, session_selector) {
      Ok(session) => proto::get_user_session_response::Result::Session(domain::user_session(session)),
      Err(reason) => proto::get_user_session_response::Result::Error(proto::DeviceEntryError {
        reason: domain::device_entry_error_reason(reason) as i32,
      }),
    };

    Ok(Response::new(proto::GetUserSessionResponse {
      result: Some(result),
    }))
  }

  async fn ensure_user_session_unlocked(
    &self,
    request: Request<proto::EnsureUserSessionUnlockedRequest>,
  ) -> Result<Response<proto::EnsureUserSessionUnlockedResponse>, Status> {
    let caller = authentication::device_entry_caller(&request)?.clone();
    let target = match request.into_inner().target {
      Some(proto::ensure_user_session_unlocked_request::Target::User(user)) if !user.trim().is_empty() => {
        auv::devices::UserSessionTarget::User(user)
      }
      Some(proto::ensure_user_session_unlocked_request::Target::SessionSelector(selector)) if !selector.trim().is_empty() => {
        auv::devices::UserSessionTarget::SessionSelector(selector)
      }
      _ => return Err(Status::invalid_argument("exactly one non-empty user or session_selector is required")),
    };
    let result = match self.daemon.ensure_user_session_unlocked(&caller, target).await {
      Ok(effect) => proto::ensure_user_session_unlocked_response::Result::Effect(domain::ensure_user_session_unlocked_effect(effect)),
      Err(reason) => proto::ensure_user_session_unlocked_response::Result::Error(proto::DeviceEntryError {
        reason: domain::device_entry_error_reason(reason) as i32,
      }),
    };

    Ok(Response::new(proto::EnsureUserSessionUnlockedResponse {
      result: Some(result),
    }))
  }

  async fn ensure_user_session_locked(
    &self,
    request: Request<proto::EnsureUserSessionLockedRequest>,
  ) -> Result<Response<proto::EnsureUserSessionLockedResponse>, Status> {
    let caller = authentication::device_entry_caller(&request)?.clone();
    let target = match request.into_inner().target {
      Some(proto::ensure_user_session_locked_request::Target::User(user)) if !user.trim().is_empty() => {
        auv::devices::UserSessionTarget::User(user)
      }
      Some(proto::ensure_user_session_locked_request::Target::SessionSelector(selector)) if !selector.trim().is_empty() => {
        auv::devices::UserSessionTarget::SessionSelector(selector)
      }
      _ => return Err(Status::invalid_argument("exactly one non-empty user or session_selector is required")),
    };
    let result = match self.daemon.ensure_user_session_locked(&caller, target).await {
      Ok(effect) => proto::ensure_user_session_locked_response::Result::Effect(domain::ensure_user_session_locked_effect(effect)),
      Err(reason) => proto::ensure_user_session_locked_response::Result::Error(proto::DeviceEntryError {
        reason: domain::device_entry_error_reason(reason) as i32,
      }),
    };

    Ok(Response::new(proto::EnsureUserSessionLockedResponse {
      result: Some(result),
    }))
  }
}