1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
use std::collections::HashMap;
use std::fmt;
use std::sync::Arc;

use crate::errors::Error;

/// Represents the rustc compiler version and the plugin interface version.
/// When a plugin is loaded, the `VersionInfo` of the server is
/// cross-referenced against the `VersionInfo` exported by the plugin. If they
/// don't match, the plugin fails.
///
/// The rustc version must match exactly because rust doesn't expose a stable
/// ABI, and there is a risk of it changing in any version. The plugin
/// interface version is also exposed to allow for changes to the interface in
/// the future. If the interface is changed, this value should be incremented.
#[derive(Debug, Eq, PartialEq)]
pub struct VersionInfo {
    /// The version of rustc used to compile. This must match because rust
    /// doesn't expose a stable ABI, and there is a risk of it changing in any
    /// version.
    pub rustc: String,
    /// The plugin interface version is also exposed to allow for changes to
    /// the interface in the future.
    pub plugin_interface: u8,
}

impl Default for VersionInfo {
    fn default() -> Self {
        Self {
            rustc: env!("RUSTC_VERSION").to_string(),
            // If the interface is changed, this value should be incremented.
            plugin_interface: 0,
        }
    }
}

impl fmt::Display for VersionInfo {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "rustc={}, plugin_interface={}", self.rustc, self.plugin_interface)
    }
}

/// Plugins should implement this trait.
pub trait Plugin: Send + Sync + 'static {
    /// Executes the plugin. Returns JSON that will be sent back to the
    /// calling client.
    ///
    /// # Arguments
    /// * `datastore`: The datastore.
    /// * `arg`: The argument from the calling client.
    fn call(
        &self,
        datastore: Arc<dyn indradb::Datastore + Send + Sync + 'static>,
        arg: serde_json::Value,
    ) -> Result<serde_json::Value, Error>;
}

/// A declaration of a plugin.
pub struct PluginDeclaration {
    pub version_info: VersionInfo,
    pub entries: HashMap<String, Box<dyn Plugin>>,
}

/// Libraries use this macro to register their plugins.
#[macro_export]
macro_rules! register_plugins {
    ( $indradb_interface_version:expr, $( $name:expr, $t:expr ),* ) => {
        #[doc(hidden)]
        #[no_mangle]
        pub unsafe extern "C" fn register() -> $crate::PluginDeclaration {
            use std::collections::HashMap;
            let mut entries = HashMap::new();
            $(
                {
                    let t: Box<dyn $crate::Plugin> = $t;
                    entries.insert($name.to_string(), t);
                }
            )*
            $crate::PluginDeclaration {
                version_info: $crate::VersionInfo::default(),
                entries,
            }
        }
    };
}