Skip to main content

ferripfs_config/
types.rs

1// Ported from: kubo/config/types.go
2// Kubo version: v0.39.0
3// Original: https://github.com/ipfs/kubo/blob/v0.39.0/config/types.go
4//
5// Original work: Copyright (c) Protocol Labs, Inc.
6// Port: Copyright (c) 2026 ferripfs contributors
7// SPDX-License-Identifier: MIT OR Apache-2.0
8
9//! Custom types for configuration, matching Kubo's types.go
10
11use serde::{Deserialize, Deserializer, Serialize, Serializer};
12
13/// Flexible string type that accepts both a single string and array of strings
14#[derive(Debug, Clone, Default)]
15pub struct Strings(pub Vec<String>);
16
17impl Serialize for Strings {
18    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
19    where
20        S: Serializer,
21    {
22        if self.0.len() == 1 {
23            self.0[0].serialize(serializer)
24        } else {
25            self.0.serialize(serializer)
26        }
27    }
28}
29
30impl<'de> Deserialize<'de> for Strings {
31    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
32    where
33        D: Deserializer<'de>,
34    {
35        #[derive(Deserialize)]
36        #[serde(untagged)]
37        enum StringOrVec {
38            String(String),
39            Vec(Vec<String>),
40        }
41
42        match StringOrVec::deserialize(deserializer)? {
43            StringOrVec::String(s) => Ok(Strings(vec![s])),
44            StringOrVec::Vec(v) => Ok(Strings(v)),
45        }
46    }
47}
48
49impl Strings {
50    pub fn is_empty(&self) -> bool {
51        self.0.is_empty()
52    }
53
54    pub fn first(&self) -> Option<&String> {
55        self.0.first()
56    }
57
58    pub fn iter(&self) -> impl Iterator<Item = &String> {
59        self.0.iter()
60    }
61}
62
63/// Ternary flag: can be true, false, or default (null/missing)
64#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
65pub enum Flag {
66    False,
67    #[default]
68    Default,
69    True,
70}
71
72impl Flag {
73    /// Get the boolean value with a default
74    pub fn with_default(self, default_value: bool) -> bool {
75        match self {
76            Flag::False => false,
77            Flag::Default => default_value,
78            Flag::True => true,
79        }
80    }
81}
82
83impl Serialize for Flag {
84    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
85    where
86        S: Serializer,
87    {
88        match self {
89            Flag::False => serializer.serialize_bool(false),
90            Flag::Default => serializer.serialize_none(),
91            Flag::True => serializer.serialize_bool(true),
92        }
93    }
94}
95
96impl<'de> Deserialize<'de> for Flag {
97    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
98    where
99        D: Deserializer<'de>,
100    {
101        let opt: Option<bool> = Option::deserialize(deserializer)?;
102        Ok(match opt {
103            Some(true) => Flag::True,
104            Some(false) => Flag::False,
105            None => Flag::Default,
106        })
107    }
108}
109
110/// Priority with default (null) and disabled (false) states
111#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
112pub enum Priority {
113    #[default]
114    Default,
115    Disabled,
116    Value(i64),
117}
118
119impl Priority {
120    pub fn with_default(self, default_value: i64) -> Option<i64> {
121        match self {
122            Priority::Default => Some(default_value),
123            Priority::Disabled => None,
124            Priority::Value(v) => Some(v),
125        }
126    }
127}
128
129impl Serialize for Priority {
130    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
131    where
132        S: Serializer,
133    {
134        match self {
135            Priority::Default => serializer.serialize_none(),
136            Priority::Disabled => serializer.serialize_bool(false),
137            Priority::Value(v) => serializer.serialize_i64(*v),
138        }
139    }
140}
141
142impl<'de> Deserialize<'de> for Priority {
143    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
144    where
145        D: Deserializer<'de>,
146    {
147        #[derive(Deserialize)]
148        #[serde(untagged)]
149        enum PriorityValue {
150            Bool(bool),
151            Int(i64),
152        }
153
154        let opt: Option<PriorityValue> = Option::deserialize(deserializer)?;
155        Ok(match opt {
156            None => Priority::Default,
157            Some(PriorityValue::Bool(false)) => Priority::Disabled,
158            Some(PriorityValue::Bool(true)) => Priority::Value(1),
159            Some(PriorityValue::Int(v)) => {
160                if v <= 0 {
161                    Priority::Disabled
162                } else {
163                    Priority::Value(v)
164                }
165            }
166        })
167    }
168}
169
170/// Optional duration, stored as a string like "1h", "30m", "10s"
171#[derive(Debug, Clone, Default, Serialize, Deserialize)]
172pub struct OptionalDuration(
173    #[serde(default, skip_serializing_if = "Option::is_none")] pub Option<String>,
174);
175
176impl OptionalDuration {
177    pub fn is_none(&self) -> bool {
178        self.0.is_none()
179    }
180
181    pub fn as_str(&self) -> Option<&str> {
182        self.0.as_deref()
183    }
184}
185
186/// Optional integer
187#[derive(Debug, Clone, Default, Serialize, Deserialize)]
188pub struct OptionalInteger(
189    #[serde(default, skip_serializing_if = "Option::is_none")] pub Option<i64>,
190);
191
192impl OptionalInteger {
193    pub fn is_none(&self) -> bool {
194        self.0.is_none()
195    }
196
197    pub fn value(&self) -> Option<i64> {
198        self.0
199    }
200
201    pub fn with_default(&self, default: i64) -> i64 {
202        self.0.unwrap_or(default)
203    }
204}
205
206/// Optional string
207#[derive(Debug, Clone, Default, Serialize, Deserialize)]
208pub struct OptionalString(
209    #[serde(default, skip_serializing_if = "Option::is_none")] pub Option<String>,
210);
211
212impl OptionalString {
213    pub fn is_none(&self) -> bool {
214        self.0.is_none()
215    }
216
217    pub fn as_str(&self) -> Option<&str> {
218        self.0.as_deref()
219    }
220
221    pub fn with_default<'a>(&'a self, default: &'a str) -> &'a str {
222        self.0.as_deref().unwrap_or(default)
223    }
224}
225
226/// Optional bytes size, stored as a string like "10GB", "1MB"
227#[derive(Debug, Clone, Default, Serialize, Deserialize)]
228pub struct OptionalBytes(
229    #[serde(default, skip_serializing_if = "Option::is_none")] pub Option<String>,
230);
231
232impl OptionalBytes {
233    pub fn is_none(&self) -> bool {
234        self.0.is_none()
235    }
236
237    pub fn as_str(&self) -> Option<&str> {
238        self.0.as_deref()
239    }
240}
241
242#[cfg(test)]
243mod tests {
244    use super::*;
245
246    #[test]
247    fn test_strings_single() {
248        let s: Strings = serde_json::from_str(r#""/ip4/0.0.0.0/tcp/4001""#).unwrap();
249        assert_eq!(s.0.len(), 1);
250        assert_eq!(s.0[0], "/ip4/0.0.0.0/tcp/4001");
251    }
252
253    #[test]
254    fn test_strings_array() {
255        let s: Strings =
256            serde_json::from_str(r#"["/ip4/0.0.0.0/tcp/4001", "/ip6/::/tcp/4001"]"#).unwrap();
257        assert_eq!(s.0.len(), 2);
258    }
259
260    #[test]
261    fn test_flag() {
262        let f: Flag = serde_json::from_str("true").unwrap();
263        assert_eq!(f, Flag::True);
264
265        let f: Flag = serde_json::from_str("false").unwrap();
266        assert_eq!(f, Flag::False);
267
268        let f: Flag = serde_json::from_str("null").unwrap();
269        assert_eq!(f, Flag::Default);
270    }
271
272    #[test]
273    fn test_priority() {
274        let p: Priority = serde_json::from_str("100").unwrap();
275        assert_eq!(p, Priority::Value(100));
276
277        let p: Priority = serde_json::from_str("false").unwrap();
278        assert_eq!(p, Priority::Disabled);
279
280        let p: Priority = serde_json::from_str("null").unwrap();
281        assert_eq!(p, Priority::Default);
282    }
283}