1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
/// Type of proxy group
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ProxyGroupType {
Select,
URLTest,
Fallback,
LoadBalance,
Relay,
SSID,
Smart,
}
impl ProxyGroupType {
/// Get string representation of the proxy group type
pub fn as_str(&self) -> &'static str {
match self {
ProxyGroupType::Select => "select",
ProxyGroupType::URLTest => "url-test",
ProxyGroupType::LoadBalance => "load-balance",
ProxyGroupType::Fallback => "fallback",
ProxyGroupType::Relay => "relay",
ProxyGroupType::SSID => "ssid",
ProxyGroupType::Smart => "smart",
}
}
}
/// Load balancing strategy
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum BalanceStrategy {
ConsistentHashing,
RoundRobin,
}
impl BalanceStrategy {
/// Get string representation of the balance strategy
pub fn as_str(&self) -> &'static str {
match self {
BalanceStrategy::ConsistentHashing => "consistent-hashing",
BalanceStrategy::RoundRobin => "round-robin",
}
}
}
/// Configuration for a proxy group
#[derive(Debug, Clone)]
pub struct ProxyGroupConfig {
/// Name of the proxy group
pub name: String,
/// Type of the proxy group
pub group_type: ProxyGroupType,
/// List of proxy names in this group
pub proxies: Vec<String>,
/// List of provider names used by this group
pub using_provider: Vec<String>,
/// URL for testing
pub url: String,
/// Interval in seconds between tests
pub interval: u32,
/// Timeout in seconds for tests
pub timeout: u32,
/// Tolerance value for tests
pub tolerance: u32,
/// Strategy for load balancing
pub strategy: BalanceStrategy,
/// Whether to use lazy loading
pub lazy: bool,
/// Whether to disable UDP support
pub disable_udp: bool,
/// Whether to persist connections
pub persistent: bool,
/// Whether to evaluate before use
pub evaluate_before_use: bool,
}
impl Default for ProxyGroupConfig {
fn default() -> Self {
Self {
name: String::new(),
group_type: ProxyGroupType::Select,
proxies: Vec::new(),
using_provider: Vec::new(),
url: String::new(),
interval: 0,
timeout: 0,
tolerance: 0,
strategy: BalanceStrategy::ConsistentHashing,
lazy: false,
disable_udp: false,
persistent: false,
evaluate_before_use: false,
}
}
}
impl ProxyGroupConfig {
/// Create a new proxy group config
pub fn new(name: String, group_type: ProxyGroupType) -> Self {
Self {
name,
group_type,
..Default::default()
}
}
/// Get string representation of the group type
pub fn type_str(&self) -> &'static str {
self.group_type.as_str()
}
/// Get string representation of the balance strategy
pub fn strategy_str(&self) -> &'static str {
self.strategy.as_str()
}
}
/// A collection of proxy group configurations
pub type ProxyGroupConfigs = Vec<ProxyGroupConfig>;
use serde::ser::SerializeStruct;
use serde::{Serialize, Serializer};
impl Serialize for ProxyGroupConfig {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
// Figure out how many fields we'll have based on the proxy group type
let mut field_count = 2; // name and type always present
// Count conditional fields based on group type
match self.group_type {
ProxyGroupType::LoadBalance => {
field_count += 4; // strategy, url, interval, tolerance
if !self.lazy {
field_count += 1; // lazy
}
}
ProxyGroupType::URLTest | ProxyGroupType::Smart => {
field_count += 2; // url, interval
if !self.lazy {
field_count += 1; // lazy
}
if self.tolerance > 0 {
field_count += 1; // tolerance
}
}
ProxyGroupType::Fallback => {
field_count += 2; // url, interval
if self.tolerance > 0 {
field_count += 1; // tolerance
}
}
_ => {}
}
// Add count for other optional fields
if self.disable_udp {
field_count += 1;
}
if self.persistent {
field_count += 1;
}
if self.evaluate_before_use {
field_count += 1;
}
// Add fields for proxies and provider
if !self.proxies.is_empty() {
field_count += 1;
}
if !self.using_provider.is_empty() {
field_count += 1;
}
// Create serialization struct
let mut state = serializer.serialize_struct("ProxyGroup", field_count)?;
// Always include name
state.serialize_field("name", &self.name)?;
// Handle type (with special case for Smart)
let type_str = if self.group_type == ProxyGroupType::Smart {
"url-test"
} else {
self.type_str()
};
state.serialize_field("type", &type_str)?;
// Add fields based on type
match self.group_type {
ProxyGroupType::LoadBalance => {
// Load balancing specific fields
state.serialize_field("strategy", &self.strategy_str())?;
if !self.lazy {
state.serialize_field("lazy", &self.lazy)?;
}
state.serialize_field("url", &self.url)?;
if self.interval > 0 {
state.serialize_field("interval", &self.interval)?;
}
if self.tolerance > 0 {
state.serialize_field("tolerance", &self.tolerance)?;
}
}
ProxyGroupType::URLTest | ProxyGroupType::Smart => {
// URL-test specific fields
if !self.lazy {
state.serialize_field("lazy", &self.lazy)?;
}
state.serialize_field("url", &self.url)?;
if self.interval > 0 {
state.serialize_field("interval", &self.interval)?;
}
if self.tolerance > 0 {
state.serialize_field("tolerance", &self.tolerance)?;
}
}
ProxyGroupType::Fallback => {
// Fallback specific fields
state.serialize_field("url", &self.url)?;
if self.interval > 0 {
state.serialize_field("interval", &self.interval)?;
}
if self.tolerance > 0 {
state.serialize_field("tolerance", &self.tolerance)?;
}
}
_ => {}
}
// Add optional common fields
if self.disable_udp {
state.serialize_field("disable-udp", &self.disable_udp)?;
}
if self.persistent {
state.serialize_field("persistent", &self.persistent)?;
}
if self.evaluate_before_use {
state.serialize_field("evaluate-before-use", &self.evaluate_before_use)?;
}
// Add proxies list if not empty
if !self.proxies.is_empty() {
state.serialize_field("proxies", &self.proxies)?;
}
// Add provider via "use" field if present
if !self.using_provider.is_empty() {
state.serialize_field("use", &self.using_provider)?;
}
state.end()
}
}