1use super::Version;
7use crate::error::CoreError;
8use std::collections::BTreeSet;
9
10#[cfg(feature = "serialization")]
11use serde::{Deserialize, Serialize};
12
13#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
15#[derive(Debug, Clone)]
16pub struct ClientCapabilities {
17 pub preferred_version: Option<Version>,
19 pub supportedversions: Vec<Version>,
21 pub required_features: BTreeSet<String>,
23 pub optional_features: BTreeSet<String>,
25 pub client_type: String,
27 pub clientversion: Version,
29}
30
31impl ClientCapabilities {
32 pub fn new(client_type: String, clientversion: Version) -> Self {
34 Self {
35 preferred_version: None,
36 supportedversions: Vec::new(),
37 required_features: BTreeSet::new(),
38 optional_features: BTreeSet::new(),
39 client_type,
40 clientversion,
41 }
42 }
43
44 pub fn with_preferred_version(mut self, version: Version) -> Self {
46 self.preferred_version = Some(version);
47 self
48 }
49
50 pub fn with_supported_version(mut self, version: Version) -> Self {
52 self.supportedversions.push(version);
53 self
54 }
55
56 pub fn require_feature(mut self, feature: &str) -> Self {
58 self.required_features.insert(feature.to_string());
59 self
60 }
61
62 pub fn prefer_feature(mut self, feature: &str) -> Self {
64 self.optional_features.insert(feature.to_string());
65 self
66 }
67}
68
69#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
71#[derive(Debug, Clone)]
72pub struct NegotiationResult {
73 pub negotiated_version: Version,
75 pub available_features: BTreeSet<String>,
77 pub unsupported_features: BTreeSet<String>,
79 pub status: NegotiationStatus,
81 pub metadata: NegotiationMetadata,
83}
84
85#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
87#[derive(Debug, Clone, Copy, PartialEq, Eq)]
88pub enum NegotiationStatus {
89 Success,
91 SuccessWithWarnings,
93 PartialSuccess,
95 Failed,
97 FeaturesMissing,
99}
100
101#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
103#[derive(Debug, Clone)]
104pub struct NegotiationMetadata {
105 pub consideredversions: Vec<Version>,
107 pub selection_reason: String,
109 pub warnings: Vec<String>,
111 pub algorithm: String,
113}
114
115#[derive(Debug, Clone, Copy, PartialEq, Eq)]
117pub enum NegotiationStrategy {
118 PreferLatest,
120 PreferClientPreference,
122 PreferStable,
124 PreferFeatureRich,
126 Custom,
128}
129
130pub struct VersionNegotiator {
132 strategy: NegotiationStrategy,
134 featurematrix: FeatureMatrix,
136}
137
138impl VersionNegotiator {
139 pub fn new() -> Self {
141 Self {
142 strategy: NegotiationStrategy::PreferLatest,
143 featurematrix: FeatureMatrix::new(),
144 }
145 }
146
147 pub fn with_strategy(mut self, strategy: NegotiationStrategy) -> Self {
149 self.strategy = strategy;
150 self
151 }
152
153 pub fn negotiate(
155 &self,
156 client_capabilities: &ClientCapabilities,
157 serverversions: &[&Version],
158 ) -> Result<NegotiationResult, CoreError> {
159 let mut metadata = NegotiationMetadata {
160 consideredversions: serverversions.iter().map(|v| (*v).clone()).collect(),
161 selection_reason: String::new(),
162 warnings: Vec::new(),
163 algorithm: format!("{:?}", self.strategy),
164 };
165
166 let compatibleversions =
168 self.find_compatibleversions(client_capabilities, serverversions, &mut metadata)?;
169
170 if compatibleversions.is_empty() {
171 return Ok(NegotiationResult {
172 negotiated_version: Version::new(0, 0, 0),
173 available_features: BTreeSet::new(),
174 unsupported_features: client_capabilities.required_features.clone(),
175 status: NegotiationStatus::Failed,
176 metadata,
177 });
178 }
179
180 let selected_version =
182 self.apply_strategy(&compatibleversions, client_capabilities, &mut metadata)?;
183
184 let (available_features, unsupported_features, status) = self.check_feature_compatibility(
186 &selected_version,
187 client_capabilities,
188 &mut metadata,
189 )?;
190
191 metadata.selection_reason = format!(
192 "Selected {} using {:?} strategy",
193 selected_version, self.strategy
194 );
195
196 Ok(NegotiationResult {
197 negotiated_version: selected_version,
198 available_features,
199 unsupported_features,
200 status,
201 metadata,
202 })
203 }
204
205 fn find_compatibleversions(
207 &self,
208 client_capabilities: &ClientCapabilities,
209 serverversions: &[&Version],
210 metadata: &mut NegotiationMetadata,
211 ) -> Result<Vec<Version>, CoreError> {
212 let mut compatible = Vec::new();
213
214 for server_version in serverversions {
215 if client_capabilities.supportedversions.is_empty()
217 || client_capabilities
218 .supportedversions
219 .contains(server_version)
220 {
221 compatible.push((*server_version).clone());
222 } else {
223 metadata.warnings.push(format!(
224 "Version {server_version} not in client's supported list"
225 ));
226 }
227 }
228
229 Ok(compatible)
230 }
231
232 fn apply_strategy(
234 &self,
235 compatibleversions: &[Version],
236 client_capabilities: &ClientCapabilities,
237 metadata: &mut NegotiationMetadata,
238 ) -> Result<Version, CoreError> {
239 match self.strategy {
240 NegotiationStrategy::PreferLatest => {
241 let mut versions = compatibleversions.to_vec();
242 versions.sort();
243 versions.reverse();
244 Ok(versions.into_iter().next().unwrap())
245 }
246 NegotiationStrategy::PreferClientPreference => {
247 if let Some(ref preferred) = client_capabilities.preferred_version {
248 if compatibleversions.contains(preferred) {
249 return Ok(preferred.clone());
250 }
251 metadata.warnings.push(
252 "Client preferred _version not available, falling back to latest"
253 .to_string(),
254 );
255 }
256 let mut versions = compatibleversions.to_vec();
258 versions.sort();
259 versions.reverse();
260 Ok(versions.into_iter().next().unwrap())
261 }
262 NegotiationStrategy::PreferStable => {
263 let stableversions: Vec<_> = compatibleversions
265 .iter()
266 .filter(|v| v.is_stable())
267 .cloned()
268 .collect();
269
270 if !stableversions.is_empty() {
271 let mut versions = stableversions;
272 versions.sort();
273 versions.reverse();
274 Ok(versions.into_iter().next().unwrap())
275 } else {
276 let mut versions = compatibleversions.to_vec();
278 versions.sort();
279 versions.reverse();
280 Ok(versions.into_iter().next().unwrap())
281 }
282 }
283 NegotiationStrategy::PreferFeatureRich => {
284 let mut versions = compatibleversions.to_vec();
287 versions.sort();
288 versions.reverse();
289 Ok(versions.into_iter().next().unwrap())
290 }
291 NegotiationStrategy::Custom => {
292 let mut versions = compatibleversions.to_vec();
294 versions.sort();
295 versions.reverse();
296 Ok(versions.into_iter().next().unwrap())
297 }
298 }
299 }
300
301 fn check_feature_compatibility(
303 &self,
304 selected_version: &Version,
305 client_capabilities: &ClientCapabilities,
306 _metadata: &mut NegotiationMetadata,
307 ) -> Result<(BTreeSet<String>, BTreeSet<String>, NegotiationStatus), CoreError> {
308 let available_features = self.featurematrix.get_supported_features(selected_version);
309 let unsupported_features: BTreeSet<String> = client_capabilities
310 .required_features
311 .difference(&available_features)
312 .cloned()
313 .collect();
314
315 let status = if unsupported_features.is_empty() {
316 NegotiationStatus::Success
317 } else {
318 NegotiationStatus::FeaturesMissing
319 };
320
321 Ok((available_features, unsupported_features, status))
322 }
323}
324
325impl Default for VersionNegotiator {
326 fn default() -> Self {
327 Self::new()
328 }
329}
330
331struct FeatureMatrix {
333 version_features: std::collections::HashMap<Version, BTreeSet<String>>,
335}
336
337impl FeatureMatrix {
338 fn new() -> Self {
339 Self {
340 version_features: std::collections::HashMap::new(),
341 }
342 }
343
344 fn get_supported_features(&self, version: &Version) -> BTreeSet<String> {
345 self.version_features
346 .get(version)
347 .cloned()
348 .unwrap_or_else(BTreeSet::new)
349 }
350
351 #[allow(dead_code)]
352 fn set_version_features(&mut self, version: Version, features: BTreeSet<String>) {
353 self.version_features.insert(version, features);
354 }
355}
356
357pub struct ClientRequirementsBuilder {
359 capabilities: ClientCapabilities,
360}
361
362impl ClientRequirementsBuilder {
363 pub fn new(client_type: &str, clientversion: Version) -> Self {
365 Self {
366 capabilities: ClientCapabilities::new(client_type.to_string(), clientversion),
367 }
368 }
369
370 pub fn preferred_version(mut self, version: Version) -> Self {
372 self.capabilities.preferred_version = Some(version);
373 self
374 }
375
376 pub fn supportversions(mut self, versions: Vec<Version>) -> Self {
378 self.capabilities.supportedversions = versions;
379 self
380 }
381
382 pub fn require_feature(mut self, feature: &str) -> Self {
384 self.capabilities
385 .required_features
386 .insert(feature.to_string());
387 self
388 }
389
390 pub fn prefer_feature(mut self, feature: &str) -> Self {
392 self.capabilities
393 .optional_features
394 .insert(feature.to_string());
395 self
396 }
397
398 pub fn build(self) -> ClientCapabilities {
400 self.capabilities
401 }
402}
403
404#[cfg(test)]
405mod tests {
406 use super::*;
407
408 #[test]
409 fn test_client_capabilities_builder() {
410 let capabilities = ClientRequirementsBuilder::new("test_client", Version::new(1, 0, 0))
411 .preferred_version(Version::new(2, 1, 0))
412 .supportversions(vec![Version::new(2, 0, 0), Version::new(2, 1, 0)])
413 .require_feature("feature1")
414 .prefer_feature("feature2")
415 .build();
416
417 assert_eq!(capabilities.client_type, "test_client");
418 assert_eq!(capabilities.preferred_version, Some(Version::new(2, 1, 0)));
419 assert!(capabilities.required_features.contains("feature1"));
420 assert!(capabilities.optional_features.contains("feature2"));
421 }
422
423 #[test]
424 fn test_version_negotiation_prefer_latest() {
425 let negotiator = VersionNegotiator::new().with_strategy(NegotiationStrategy::PreferLatest);
426
427 let client_capabilities =
428 ClientRequirementsBuilder::new("test_client", Version::new(1, 0, 0))
429 .supportversions(vec![
430 Version::new(1, 0, 0),
431 Version::new(1, 1, 0),
432 Version::new(2, 0, 0),
433 ])
434 .build();
435
436 let v1 = Version::new(1, 0, 0);
437 let v2 = Version::new(1, 1, 0);
438 let v3 = Version::new(2, 0, 0);
439 let serverversions = vec![&v1, &v2, &v3];
440
441 let result = negotiator
442 .negotiate(&client_capabilities, &serverversions)
443 .unwrap();
444 assert_eq!(result.negotiated_version, Version::new(2, 0, 0));
445 assert_eq!(result.status, NegotiationStatus::Success);
446 }
447
448 #[test]
449 fn test_version_negotiation_prefer_client() {
450 let negotiator =
451 VersionNegotiator::new().with_strategy(NegotiationStrategy::PreferClientPreference);
452
453 let client_capabilities =
454 ClientRequirementsBuilder::new("test_client", Version::new(1, 0, 0))
455 .preferred_version(Version::new(1, 1, 0))
456 .supportversions(vec![
457 Version::new(1, 0, 0),
458 Version::new(1, 1, 0),
459 Version::new(2, 0, 0),
460 ])
461 .build();
462
463 let v1 = Version::new(1, 0, 0);
464 let v2 = Version::new(1, 1, 0);
465 let v3 = Version::new(2, 0, 0);
466 let serverversions = vec![&v1, &v2, &v3];
467
468 let result = negotiator
469 .negotiate(&client_capabilities, &serverversions)
470 .unwrap();
471 assert_eq!(result.negotiated_version, Version::new(1, 1, 0));
472 }
473
474 #[test]
475 fn test_version_negotiation_prefer_stable() {
476 let negotiator = VersionNegotiator::new().with_strategy(NegotiationStrategy::PreferStable);
477
478 let client_capabilities =
479 ClientRequirementsBuilder::new("test_client", Version::new(1, 0, 0))
480 .supportversions(vec![
481 Version::new(1, 0, 0),
482 Version::parse("2.0.0-alpha").unwrap(),
483 Version::new(1, 1, 0),
484 ])
485 .build();
486
487 let v1 = Version::new(1, 0, 0);
488 let v2 = Version::parse("2.0.0-alpha").unwrap();
489 let v3 = Version::new(1, 1, 0);
490 let serverversions = vec![&v1, &v2, &v3];
491
492 let result = negotiator
493 .negotiate(&client_capabilities, &serverversions)
494 .unwrap();
495 assert_eq!(result.negotiated_version, Version::new(1, 1, 0));
497 }
498
499 #[test]
500 fn test_no_compatible_version() {
501 let negotiator = VersionNegotiator::new();
502
503 let client_capabilities =
504 ClientRequirementsBuilder::new("test_client", Version::new(1, 0, 0))
505 .supportversions(vec![Version::new(3, 0, 0)])
506 .build();
507
508 let v1 = Version::new(1, 0, 0);
509 let v2 = Version::new(2, 0, 0);
510 let serverversions = vec![&v1, &v2];
511
512 let result = negotiator
513 .negotiate(&client_capabilities, &serverversions)
514 .unwrap();
515 assert_eq!(result.status, NegotiationStatus::Failed);
516 }
517
518 #[test]
519 fn test_negotiation_metadata() {
520 let negotiator = VersionNegotiator::new();
521
522 let client_capabilities =
523 ClientRequirementsBuilder::new("test_client", Version::new(1, 0, 0))
524 .supportversions(vec![Version::new(1, 0, 0)])
525 .build();
526
527 let v1 = Version::new(1, 0, 0);
528 let serverversions = vec![&v1];
529
530 let result = negotiator
531 .negotiate(&client_capabilities, &serverversions)
532 .unwrap();
533 assert!(!result.metadata.selection_reason.is_empty());
534 assert_eq!(result.metadata.consideredversions.len(), 1);
535 }
536}