covey-plugin 0.0.2

APIs for creating Covey plugins
Documentation
use std::process;

use covey_proto::plugin_server::PluginServer;
use parking_lot::Mutex;
use tokio::{net::TcpListener, sync::RwLock, task::LocalSet};
use tonic::transport::Server;

use crate::{Plugin, store::ListItemStore};

/// Wrapper around a [`Plugin`] that implements the tonic server.
pub(crate) struct ServerState<P> {
    pub(crate) plugin: RwLock<Option<P>>,
    pub(crate) list_item_store: Mutex<ListItemStore>,
}

impl<T: Plugin> ServerState<T> {
    /// Creates a new uninitialised plugin.
    //
    /// The `initialise` RPC method must be called to fill in the plugin.
    pub(crate) fn new_empty() -> Self {
        Self {
            plugin: RwLock::new(None),
            list_item_store: Mutex::new(ListItemStore::new()),
        }
    }
}

/// Starts up the server with a specified plugin implementation.
///
/// The plugin id should be `env!("CARGO_PKG_NAME")`.
///
/// This will start a single-threaded tokio runtime.
///
/// # Examples
/// See this crate's README.md for extra details.
///
/// Simple plugin with no configuration:
/// ```ignore
/// use covey_plugin::{clone_async, Action, Input, List, ListItem, Plugin, Result};
///
/// struct MyPlugin;
///
/// impl Plugin for MyPlugin {
///     // ignore configuration
///     type Config = ();
///
///     async fn new(config: Self::Config) -> Result<Self> {
///         Ok(Self)
///     }
///
///     async fn query(&self, query: String) -> Result<List> {
///         // do some logic here...
///         Ok(List::new(vec![
///             ListItem::new("list item title")
///                 .with_description("a description")
///                 // define callbacks that will be triggered when
///                 // the user activates a command.
///                 // this method is defined an extension trait
///                 // generated by `include_manifest!()`.
///                 .on_activate(async move || {
///                     Ok([Action::Close, Action::Copy("wahoo")])
///                 })
///                 // you will often need to clone values into
///                 // the callback. use the helper macro to make
///                 // this easier.
///                 .on_complete(clone_async!(query, || {
///                     Ok(Input::new(query))
///                 }))
///         ]))
///     }
/// }
///
/// // generates types from reading `../manifest.toml`.
/// // also defines an extension trait for methods like `.on_activate`.
/// covey_plugin::include_manifest!();
///
/// fn main() {
///     // this will run the server.
///     // it requires the name of the binary to be passed in.
///     // use `env!` to get it from the cargo project.
///     covey_plugin::run_server::<Open>(env!("CARGO_BIN_NAME"))
/// }
/// ```
pub fn run_server<T: Plugin>(plugin_id: &'static str) -> ! {
    crate::PLUGIN_ID
        .set(plugin_id)
        .expect("plugin id should only be set from main");

    let result = tokio::runtime::Builder::new_current_thread()
        .enable_all()
        .build()
        .map_err(|e| anyhow::anyhow!(e))
        .and_then(|rt| {
            let set = LocalSet::new();
            let _guard = set.enter();
            rt.block_on(set.run_until(async {
                // if port 0 is provided, asks the OS for a port
                // https://github.com/hyperium/tonic/blob/master/tests/integration_tests/tests/timeout.rs#L77-L89
                let listener = TcpListener::bind("[::1]:0").await?;
                let port = listener.local_addr()?.port();

                // print port for covey to read
                println!("{port}");

                Server::builder()
                    .add_service(PluginServer::new(ServerState::<T>::new_empty()))
                    .serve_with_incoming(tokio_stream::wrappers::TcpListenerStream::new(listener))
                    .await?;

                Ok(())
            }))
        });

    match result {
        Ok(()) => process::exit(0),
        Err(e) => {
            print_error(&e);
            process::exit(1)
        }
    }
}

fn print_error(e: &anyhow::Error) {
    let err_string = e
        .chain()
        .map(ToString::to_string)
        .collect::<Vec<_>>()
        .join("\n");
    eprintln!("{err_string}");
}