Skip to main content

tidecoin_network_kind/
lib.rs

1// SPDX-License-Identifier: CC0-1.0
2
3//! # Tidecoin network
4//!
5//! The term "network" is overloaded, here [`Network`] refers to the specific
6//! Tidecoin network we are operating on e.g., testnet, regtest. The terms
7//! "network" and "chain" are often used interchangeably for this concept.
8
9#![no_std]
10// Coding conventions.
11#![warn(missing_docs)]
12#![warn(deprecated_in_future)]
13#![doc(test(attr(warn(unused))))]
14
15#[cfg(feature = "std")]
16extern crate std;
17
18#[cfg(feature = "serde")]
19pub extern crate serde;
20
21use core::fmt;
22use core::str::FromStr;
23
24#[cfg(feature = "arbitrary")]
25use arbitrary::{Arbitrary, Unstructured};
26use internals::error::InputString;
27#[cfg(feature = "serde")]
28use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer};
29
30/// What kind of network we are on.
31#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
32#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
33pub enum NetworkKind {
34    /// The Tidecoin mainnet network.
35    Main,
36    /// Some kind of testnet network (testnet, regtest).
37    Test,
38}
39
40impl NetworkKind {
41    /// Returns true if this is real mainnet tidecoin.
42    pub const fn is_mainnet(self) -> bool {
43        matches!(self, Self::Main)
44    }
45}
46
47impl From<Network> for NetworkKind {
48    fn from(network: Network) -> Self {
49        match network {
50            Network::Tidecoin => Self::Main,
51            Network::Testnet | Network::Regtest => Self::Test,
52        }
53    }
54}
55
56/// The cryptocurrency network to act on.
57///
58/// This is an exhaustive enum, meaning that we cannot add any future networks without defining a
59/// new, incompatible version of this type. If you are using this type directly and wish to support the
60/// new network, this will be a breaking change to your APIs and likely require changes in your code.
61///
62/// If you are concerned about forward compatibility, consider using `T: Into<Params>` instead of
63/// this type as a parameter to functions in your public API, or directly using the `Params` type.
64#[derive(Copy, PartialEq, Eq, PartialOrd, Ord, Clone, Hash, Debug)]
65pub enum Network {
66    /// Mainnet Tidecoin.
67    Tidecoin,
68    /// Tidecoin's testnet network.
69    Testnet,
70    /// Tidecoin's regtest network.
71    Regtest,
72}
73
74#[cfg(feature = "serde")]
75impl Serialize for Network {
76    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
77    where
78        S: Serializer,
79    {
80        serializer.serialize_str(self.as_display_str())
81    }
82}
83
84#[cfg(feature = "serde")]
85impl<'de> Deserialize<'de> for Network {
86    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
87    where
88        D: Deserializer<'de>,
89    {
90        struct NetworkVisitor;
91
92        impl Visitor<'_> for NetworkVisitor {
93            type Value = Network;
94
95            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
96                formatter.write_str("a valid network identifier")
97            }
98
99            fn visit_str<E>(self, value: &str) -> Result<Network, E>
100            where
101                E: serde::de::Error,
102            {
103                Network::from_str(value).map_err(E::custom)
104            }
105        }
106
107        deserializer.deserialize_str(NetworkVisitor)
108    }
109}
110
111impl Network {
112    /// Converts a `Network` to its equivalent `-chain` argument name.
113    ///
114    /// Allowed values: main, test, regtest
115    pub fn to_core_arg(self) -> &'static str {
116        match self {
117            Self::Tidecoin => "main",
118            Self::Testnet => "test",
119            Self::Regtest => "regtest",
120        }
121    }
122
123    /// Converts a `-chain` argument name to its equivalent `Network`.
124    ///
125    /// # Errors
126    ///
127    /// Errors if input is not exactly one of:
128    /// * `main`
129    /// * `test`
130    /// * `regtest`
131    pub fn from_core_arg(core_arg: &str) -> Result<Self, ParseNetworkError> {
132        let network = match core_arg {
133            "main" => Self::Tidecoin,
134            "test" => Self::Testnet,
135            "regtest" => Self::Regtest,
136            _ => return Err(ParseNetworkError(InputString::from(core_arg))),
137        };
138        Ok(network)
139    }
140
141    /// Returns a string representation of the `Network` enum variant.
142    const fn as_display_str(self) -> &'static str {
143        match self {
144            Self::Tidecoin => "tidecoin",
145            Self::Testnet => "testnet",
146            Self::Regtest => "regtest",
147        }
148    }
149}
150
151impl fmt::Display for Network {
152    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
153        write!(f, "{}", self.as_display_str())
154    }
155}
156
157#[cfg(feature = "serde")]
158pub mod as_core_arg {
159    //! Module for serialization/deserialization of network variants into/from core values
160
161    // No need to document these functions, they are well known.
162    #![allow(missing_docs)]
163    #![allow(clippy::missing_errors_doc)]
164
165    use crate::Network;
166
167    #[allow(clippy::trivially_copy_pass_by_ref)] // `serde` controls the API.
168    pub fn serialize<S>(network: &Network, serializer: S) -> Result<S::Ok, S::Error>
169    where
170        S: serde::Serializer,
171    {
172        serializer.serialize_str(network.to_core_arg())
173    }
174
175    pub fn deserialize<'de, D>(deserializer: D) -> Result<Network, D::Error>
176    where
177        D: serde::Deserializer<'de>,
178    {
179        struct NetworkVisitor;
180
181        impl serde::de::Visitor<'_> for NetworkVisitor {
182            type Value = Network;
183
184            fn visit_str<E: serde::de::Error>(self, s: &str) -> Result<Self::Value, E> {
185                Network::from_core_arg(s).map_err(|_| {
186                    E::invalid_value(
187                        serde::de::Unexpected::Str(s),
188                        &"tidecoin network encoded as a string (either main, test, or regtest)",
189                    )
190                })
191            }
192
193            fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result {
194                write!(
195                    formatter,
196                    "tidecoin network encoded as a string (either main, test, or regtest)"
197                )
198            }
199        }
200
201        deserializer.deserialize_str(NetworkVisitor)
202    }
203}
204
205/// An error in parsing network string.
206#[derive(Debug, Clone, PartialEq, Eq)]
207#[non_exhaustive]
208pub struct ParseNetworkError(InputString);
209
210impl fmt::Display for ParseNetworkError {
211    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
212        // Outputs 'failed to parse <input string> as network'.
213        write!(f, "{}", self.0.display_cannot_parse("network"))
214    }
215}
216
217#[cfg(feature = "std")]
218impl std::error::Error for ParseNetworkError {
219    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
220        None
221    }
222}
223
224impl FromStr for Network {
225    type Err = ParseNetworkError;
226
227    #[inline]
228    fn from_str(s: &str) -> Result<Self, Self::Err> {
229        match s {
230            "tidecoin" | "main" => Ok(Self::Tidecoin),
231            "testnet" | "test" => Ok(Self::Testnet),
232            "regtest" => Ok(Self::Regtest),
233            _ => Err(ParseNetworkError(InputString::from(s))),
234        }
235    }
236}
237
238impl AsRef<Self> for Network {
239    fn as_ref(&self) -> &Self {
240        self
241    }
242}
243
244#[cfg(feature = "arbitrary")]
245impl<'a> Arbitrary<'a> for NetworkKind {
246    fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
247        match bool::arbitrary(u)? {
248            true => Ok(Self::Main),
249            false => Ok(Self::Test),
250        }
251    }
252}
253
254#[cfg(test)]
255mod tests {
256    #[cfg(feature = "std")]
257    use std::string::ToString;
258
259    #[cfg(feature = "serde")]
260    use serde::{Deserialize, Serialize};
261
262    use super::Network;
263
264    #[test]
265    #[cfg(feature = "std")]
266    fn string() {
267        assert_eq!(Network::Tidecoin.to_string(), "tidecoin");
268        assert_eq!(Network::Testnet.to_string(), "testnet");
269        assert_eq!(Network::Regtest.to_string(), "regtest");
270
271        assert_eq!("tidecoin".parse::<Network>().unwrap(), Network::Tidecoin);
272        assert_eq!("main".parse::<Network>().unwrap(), Network::Tidecoin);
273        assert_eq!("testnet".parse::<Network>().unwrap(), Network::Testnet);
274        assert_eq!("test".parse::<Network>().unwrap(), Network::Testnet);
275        assert_eq!("regtest".parse::<Network>().unwrap(), Network::Regtest);
276        assert!("fakenet".parse::<Network>().is_err());
277    }
278
279    #[test]
280    #[cfg(feature = "serde")]
281    #[cfg(feature = "std")]
282    fn serde_roundtrip() {
283        use std::{format, vec};
284
285        use Network::*;
286        let tests = vec![(Tidecoin, "tidecoin"), (Testnet, "testnet"), (Regtest, "regtest")];
287
288        for tc in tests {
289            let network = tc.0;
290
291            let want = format!("\"{}\"", tc.1);
292            let got = serde_json::to_string(&tc.0).expect("failed to serialize network");
293            assert_eq!(got, want);
294
295            let back: Network = serde_json::from_str(&got).expect("failed to deserialize network");
296            assert_eq!(back, network);
297        }
298    }
299
300    #[test]
301    fn from_to_core_arg() {
302        let expected_pairs = [
303            (Network::Tidecoin, "main"),
304            (Network::Testnet, "test"),
305            (Network::Regtest, "regtest"),
306        ];
307
308        for (net, core_arg) in &expected_pairs {
309            assert_eq!(Network::from_core_arg(core_arg), Ok(*net));
310            assert_eq!(net.to_core_arg(), *core_arg);
311        }
312    }
313
314    #[test]
315    #[cfg(feature = "serde")]
316    fn serde_as_core_arg() {
317        #[derive(Serialize, Deserialize, PartialEq, Debug)]
318        struct T {
319            #[serde(with = "crate::as_core_arg")]
320            pub network: Network,
321        }
322
323        serde_test::assert_tokens(
324            &T { network: Network::Tidecoin },
325            &[
326                serde_test::Token::Struct { name: "T", len: 1 },
327                serde_test::Token::Str("network"),
328                serde_test::Token::Str("main"),
329                serde_test::Token::StructEnd,
330            ],
331        );
332    }
333}