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}