Skip to main content

nu_protocol/plugin/registry_file/
mod.rs

1use std::{
2    io::{Read, Write},
3    path::PathBuf,
4};
5
6use serde::{Deserialize, Serialize};
7
8use crate::{
9    PluginIdentity, PluginMetadata, PluginSignature, ShellError, Span,
10    shell_error::generic::GenericError,
11};
12
13// This has a big impact on performance
14const BUFFER_SIZE: usize = 65536;
15
16// Chose settings at the low end, because we're just trying to get the maximum speed
17const COMPRESSION_QUALITY: u32 = 3; // 1 can be very bad
18const WIN_SIZE: u32 = 20; // recommended 20-22
19
20#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
21pub struct PluginRegistryFile {
22    /// The Nushell version that last updated the file.
23    pub nushell_version: String,
24
25    /// The installed plugins.
26    pub plugins: Vec<PluginRegistryItem>,
27}
28
29impl Default for PluginRegistryFile {
30    fn default() -> Self {
31        Self::new()
32    }
33}
34
35impl PluginRegistryFile {
36    /// Create a new, empty plugin registry file.
37    pub fn new() -> PluginRegistryFile {
38        PluginRegistryFile {
39            nushell_version: env!("CARGO_PKG_VERSION").to_owned(),
40            plugins: vec![],
41        }
42    }
43
44    /// Read the plugin registry file from a reader, e.g. [`File`](std::fs::File).
45    pub fn read_from(
46        reader: impl Read,
47        error_span: Option<Span>,
48    ) -> Result<PluginRegistryFile, ShellError> {
49        // Format is brotli compressed messagepack
50        let brotli_reader = brotli::Decompressor::new(reader, BUFFER_SIZE);
51
52        rmp_serde::from_read(brotli_reader).map_err(|err| {
53            let error = format!("Failed to load plugin file: {err}");
54            let msg = "plugin file load attempted here";
55            let help = "it may be corrupt. Try deleting it and registering your plugins again";
56            match error_span {
57                Some(span) => {
58                    ShellError::Generic(GenericError::new(error, msg, span).with_help(help))
59                }
60                None => ShellError::Generic(GenericError::new_internal(error, msg).with_help(help)),
61            }
62        })
63    }
64
65    /// Write the plugin registry file to a writer, e.g. [`File`](std::fs::File).
66    ///
67    /// The `nushell_version` will be updated to the current version before writing.
68    pub fn write_to(
69        &mut self,
70        writer: impl Write,
71        error_span: Option<Span>,
72    ) -> Result<(), ShellError> {
73        // Update the Nushell version before writing
74        env!("CARGO_PKG_VERSION").clone_into(&mut self.nushell_version);
75
76        // Format is brotli compressed messagepack
77        let mut brotli_writer =
78            brotli::CompressorWriter::new(writer, BUFFER_SIZE, COMPRESSION_QUALITY, WIN_SIZE);
79
80        rmp_serde::encode::write_named(&mut brotli_writer, self)
81            .map_err(|err| err.to_string())
82            .and_then(|_| brotli_writer.flush().map_err(|err| err.to_string()))
83            .map_err(|err| {
84                let error = "Failed to save plugin file";
85                let msg = "plugin file save attempted here";
86                match error_span {
87                    Some(span) => {
88                        ShellError::Generic(GenericError::new(error, msg, span).with_help(err))
89                    }
90                    None => {
91                        ShellError::Generic(GenericError::new_internal(error, msg).with_help(err))
92                    }
93                }
94            })
95    }
96
97    /// Insert or update a plugin in the plugin registry file.
98    pub fn upsert_plugin(&mut self, item: PluginRegistryItem) {
99        if let Some(existing_item) = self.plugins.iter_mut().find(|p| p.name == item.name) {
100            *existing_item = item;
101        } else {
102            self.plugins.push(item);
103
104            // Sort the plugins for consistency
105            self.plugins
106                .sort_by(|item1, item2| item1.name.cmp(&item2.name));
107        }
108    }
109}
110
111/// A single plugin definition from a [`PluginRegistryFile`].
112///
113/// Contains the information necessary for the [`PluginIdentity`], as well as possibly valid data
114/// about the plugin including the registered command signatures.
115#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
116pub struct PluginRegistryItem {
117    /// The name of the plugin, as would show in `plugin list`. This does not include the file
118    /// extension or the `nu_plugin_` prefix.
119    pub name: String,
120
121    /// The path to the file.
122    pub filename: PathBuf,
123
124    /// The shell program used to run the plugin, if applicable.
125    pub shell: Option<PathBuf>,
126
127    /// Additional data that might be invalid so that we don't fail to load the whole plugin file
128    /// if there's a deserialization error.
129    #[serde(flatten)]
130    pub data: PluginRegistryItemData,
131}
132
133impl PluginRegistryItem {
134    /// Create a [`PluginRegistryItem`] from an identity, metadata, and signatures.
135    pub fn new(
136        identity: &PluginIdentity,
137        metadata: PluginMetadata,
138        mut commands: Vec<PluginSignature>,
139    ) -> PluginRegistryItem {
140        // Sort the commands for consistency
141        commands.sort_by(|cmd1, cmd2| cmd1.sig.name.cmp(&cmd2.sig.name));
142
143        PluginRegistryItem {
144            name: identity.name().to_owned(),
145            filename: identity.filename().to_owned(),
146            shell: identity.shell().map(|p| p.to_owned()),
147            data: PluginRegistryItemData::Valid { metadata, commands },
148        }
149    }
150}
151
152/// Possibly valid data about a plugin in a [`PluginRegistryFile`]. If deserialization fails, it will
153/// be `Invalid`.
154#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
155#[serde(untagged)]
156pub enum PluginRegistryItemData {
157    Valid {
158        /// Metadata for the plugin, including its version.
159        #[serde(default)]
160        metadata: PluginMetadata,
161        /// Signatures and examples for each command provided by the plugin.
162        commands: Vec<PluginSignature>,
163    },
164    #[serde(
165        serialize_with = "serialize_invalid",
166        deserialize_with = "deserialize_invalid"
167    )]
168    Invalid,
169}
170
171fn serialize_invalid<S>(serializer: S) -> Result<S::Ok, S::Error>
172where
173    S: serde::Serializer,
174{
175    ().serialize(serializer)
176}
177
178fn deserialize_invalid<'de, D>(deserializer: D) -> Result<(), D::Error>
179where
180    D: serde::Deserializer<'de>,
181{
182    serde::de::IgnoredAny::deserialize(deserializer)?;
183    Ok(())
184}
185
186#[cfg(test)]
187mod tests;