nu_plugin_protocol/
protocol_info.rs

1use nu_protocol::ShellError;
2use semver::Prerelease;
3use serde::{Deserialize, Serialize};
4
5/// Protocol information, sent as a `Hello` message on initialization. This determines the
6/// compatibility of the plugin and engine. They are considered to be compatible if the lower
7/// version is semver compatible with the higher one.
8#[derive(Serialize, Deserialize, Debug, Clone)]
9pub struct ProtocolInfo {
10    /// The name of the protocol being implemented. Only one protocol is supported. This field
11    /// can be safely ignored, because not matching is a deserialization error
12    pub protocol: Protocol,
13    /// The semantic version of the protocol. This should be the version of the `nu-plugin-protocol`
14    /// crate
15    pub version: String,
16    /// Supported optional features. This helps to maintain semver compatibility when adding new
17    /// features
18    pub features: Vec<Feature>,
19}
20
21impl Default for ProtocolInfo {
22    fn default() -> ProtocolInfo {
23        ProtocolInfo {
24            protocol: Protocol::NuPlugin,
25            version: env!("CARGO_PKG_VERSION").into(),
26            features: default_features(),
27        }
28    }
29}
30
31impl ProtocolInfo {
32    /// True if the version specified in `self` is compatible with the version specified in `other`.
33    pub fn is_compatible_with(&self, other: &ProtocolInfo) -> Result<bool, ShellError> {
34        fn parse_failed(error: semver::Error) -> ShellError {
35            ShellError::PluginFailedToLoad {
36                msg: format!("Failed to parse protocol version: {error}"),
37            }
38        }
39        let mut versions = [
40            semver::Version::parse(&self.version).map_err(parse_failed)?,
41            semver::Version::parse(&other.version).map_err(parse_failed)?,
42        ];
43
44        versions.sort();
45
46        // The version may be a nightly version (1.2.3-nightly.1).
47        // It's good to mark the prerelease field as empty, so plugins
48        // can work with a nightly version of Nushell.
49        versions[1].pre = Prerelease::EMPTY;
50        versions[0].pre = Prerelease::EMPTY;
51
52        // For example, if the lower version is 1.1.0, and the higher version is 1.2.3, the
53        // requirement is that 1.2.3 matches ^1.1.0 (which it does)
54        Ok(semver::Comparator {
55            op: semver::Op::Caret,
56            major: versions[0].major,
57            minor: Some(versions[0].minor),
58            patch: Some(versions[0].patch),
59            pre: versions[0].pre.clone(),
60        }
61        .matches(&versions[1]))
62    }
63
64    /// True if the protocol info contains a feature compatible with the given feature.
65    pub fn supports_feature(&self, feature: &Feature) -> bool {
66        self.features.iter().any(|f| feature.is_compatible_with(f))
67    }
68}
69
70/// Indicates the protocol in use. Only one protocol is supported.
71#[derive(Serialize, Deserialize, Debug, Clone, Default)]
72pub enum Protocol {
73    /// Serializes to the value `"nu-plugin"`
74    #[serde(rename = "nu-plugin")]
75    #[default]
76    NuPlugin,
77}
78
79/// Indicates optional protocol features. This can help to make non-breaking-change additions to
80/// the protocol. Features are not restricted to plain strings and can contain additional
81/// configuration data.
82///
83/// Optional features should not be used by the protocol if they are not present in the
84/// [`ProtocolInfo`] sent by the other side.
85#[derive(Serialize, Deserialize, Debug, Clone)]
86#[serde(tag = "name")]
87pub enum Feature {
88    /// The plugin supports running with a local socket passed via `--local-socket` instead of
89    /// stdio.
90    LocalSocket,
91
92    /// A feature that was not recognized on deserialization. Attempting to serialize this feature
93    /// is an error. Matching against it may only be used if necessary to determine whether
94    /// unsupported features are present.
95    #[serde(other, skip_serializing)]
96    Unknown,
97}
98
99impl Feature {
100    /// True if the feature is considered to be compatible with another feature.
101    pub fn is_compatible_with(&self, other: &Feature) -> bool {
102        matches!((self, other), (Feature::LocalSocket, Feature::LocalSocket))
103    }
104}
105
106/// Protocol features compiled into this version of `nu-plugin`.
107pub fn default_features() -> Vec<Feature> {
108    vec![
109        // Only available if compiled with the `local-socket` feature flag (enabled by default).
110        #[cfg(feature = "local-socket")]
111        Feature::LocalSocket,
112    ]
113}