1use std::future::Future;
2
3use tokio::sync::mpsc;
4use tokio_stream::wrappers::ReceiverStream;
5
6use crate::{List, Menu, Result, manifest::ManifestDeserialization, server::ServerState};
7
8pub trait Plugin: Sized + Send + Sync + 'static {
9 type Config: ManifestDeserialization;
13
14 fn new(config: Self::Config) -> impl Future<Output = Result<Self>> + Send;
15
16 fn query(&self, query: String) -> impl Future<Output = Result<List>> + Send;
17}
18
19type TonicResult<T> = Result<tonic::Response<T>, tonic::Status>;
20
21#[tonic::async_trait]
22impl<T> covey_proto::plugin_server::Plugin for ServerState<T>
23where
24 T: Plugin,
25{
26 async fn initialise(
27 &self,
28 request: tonic::Request<covey_proto::InitialiseRequest>,
29 ) -> TonicResult<()> {
30 let request = request.into_inner();
31
32 let mut guard = self.plugin.write().await;
33
34 let config = ManifestDeserialization::try_from_input(&request.json)
35 .map_err(|e| tonic::Status::invalid_argument(e.to_string()))?;
36 let plugin = T::new(config).await.map_err(into_tonic_status)?;
37
38 *guard = Some(plugin);
39
40 Ok(tonic::Response::new(()))
41 }
42
43 async fn query(
44 &self,
45 request: tonic::Request<covey_proto::QueryRequest>,
46 ) -> TonicResult<covey_proto::QueryResponse> {
47 let list = self
48 .plugin
49 .read()
50 .await
51 .as_ref()
52 .expect("plugin has not been initialised")
53 .query(request.into_inner().query)
54 .await
55 .map_err(into_tonic_status)?;
56
57 Ok(tonic::Response::new(
58 self.list_item_store.lock().store_query_result(list),
59 ))
60 }
61
62 type ActivateStream = ReceiverStream<Result<covey_proto::ActivationResponse, tonic::Status>>;
63
64 async fn activate(
65 &self,
66 request: tonic::Request<covey_proto::ActivationRequest>,
67 ) -> TonicResult<Self::ActivateStream> {
68 let request = request.into_inner();
69 let id = request.selection_id;
70 let callbacks =
71 self.list_item_store
72 .lock()
73 .fetch_callbacks_of(id)
74 .ok_or(tonic::Status::data_loss(format!(
75 "failed to fetch callback of list item with id {id}"
76 )))?;
77
78 let (tx, rx) = mpsc::channel(4);
79 let menu = Menu { sender: tx };
80
81 tokio::task::spawn_local(async move {
85 callbacks.call_command(&request.command_name, menu).await;
86 })
87 .await
88 .map_err(|e| tonic::Status::internal(e.to_string()))?;
90
91 Ok(tonic::Response::new(ReceiverStream::new(rx)))
92 }
93}
94
95#[expect(
96 clippy::needless_pass_by_value,
97 reason = "easier to only use path when mapping"
98)]
99fn into_tonic_status(e: anyhow::Error) -> tonic::Status {
100 tonic::Status::unknown(
101 e.chain()
102 .map(ToString::to_string)
103 .collect::<Vec<_>>()
104 .join("\n"),
105 )
106}