nuts_tool_api/info.rs
1// MIT License
2//
3// Copyright (c) 2024 Robin Doer
4//
5// Permission is hereby granted, free of charge, to any person obtaining a copy
6// of this software and associated documentation files (the "Software"), to
7// deal in the Software without restriction, including without limitation the
8// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
9// sell copies of the Software, and to permit persons to whom the Software is
10// furnished to do so, subject to the following conditions:
11//
12// The above copyright notice and this permission notice shall be included in
13// all copies or substantial portions of the Software.
14//
15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21// IN THE SOFTWARE.
22
23#[cfg(test)]
24#[cfg(feature = "tool")]
25mod tests;
26
27use serde::{Deserialize, Deserializer, Serialize};
28
29/// Current revision of the exchange protocol between plugin and tool.
30///
31/// # History
32///
33/// ## Revision 0
34///
35/// Initial revision.
36///
37/// ## Revision 1
38///
39/// The `revision` field was added to [`PluginInfo`]. The
40/// [`crate::OkResponse::Map`] response of a [`crate::Request::PluginInfo`]
41/// request now contains a `revision` key. Therfore, without the `revision` key
42/// in the response you will have a revision `0`.
43pub const CURRENT_REVISION: u32 = 1;
44
45fn de_revision<'de, D: Deserializer<'de>>(deserializer: D) -> Result<u32, D::Error> {
46 let rev: u32 = Deserialize::deserialize(deserializer)?;
47
48 // Revision 0 has no revision field in PluginInfo. Therefore there cannot
49 // be a such a revision.
50
51 if rev > 0 {
52 Ok(rev)
53 } else {
54 Err(serde::de::Error::custom(
55 "revision 0 cannot be explicity specified",
56 ))
57 }
58}
59
60/// Information of a plugin
61#[derive(Debug, Deserialize, Serialize)]
62pub struct PluginInfo {
63 name: String,
64 version: String,
65 #[serde(default)]
66 #[serde(deserialize_with = "de_revision")]
67 revision: u32,
68}
69
70impl PluginInfo {
71 /// Creates a new `PluginInfo` instance.
72 pub fn new<N: AsRef<str>, V: AsRef<str>>(name: N, version: V) -> PluginInfo {
73 PluginInfo {
74 name: name.as_ref().to_string(),
75 version: version.as_ref().to_string(),
76 revision: CURRENT_REVISION,
77 }
78 }
79
80 /// Returns the name of the plugin.
81 pub fn name(&self) -> &str {
82 &self.name
83 }
84
85 /// Returns the version of the plugin.
86 pub fn version(&self) -> &str {
87 &self.version
88 }
89
90 /// Returns the protocol revision.
91 pub fn revision(&self) -> u32 {
92 self.revision
93 }
94}
95
96#[cfg(feature = "tool")]
97mod tool_impls {
98 use std::collections::HashMap;
99 use std::convert::TryFrom;
100
101 use crate::info::PluginInfo;
102 use crate::tool::PluginError::{self, InvalidResponse};
103
104 impl TryFrom<HashMap<String, String>> for PluginInfo {
105 type Error = PluginError;
106
107 fn try_from(mut map: HashMap<String, String>) -> Result<Self, PluginError> {
108 let name = map.remove("name").ok_or(InvalidResponse)?;
109 let version = map.remove("version").ok_or(InvalidResponse)?;
110
111 // In revision 0 the revision field is missing.
112 let revision = map
113 .remove("revision")
114 .map_or(Ok(Default::default()), |s| s.parse())
115 .map_err(|_| InvalidResponse)?;
116
117 Ok(PluginInfo {
118 name,
119 version,
120 revision,
121 })
122 }
123 }
124}