scirs2_core/versioning/
negotiation.rs

1//! # Version Negotiation
2//!
3//! Version negotiation system for client-server compatibility
4//! and automatic version selection in distributed systems.
5
6use super::Version;
7use crate::error::CoreError;
8use std::collections::BTreeSet;
9
10#[cfg(feature = "serialization")]
11use serde::{Deserialize, Serialize};
12
13/// Client capabilities for version negotiation
14#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
15#[derive(Debug, Clone)]
16pub struct ClientCapabilities {
17    /// Preferred version
18    pub preferred_version: Option<Version>,
19    /// Supported version range
20    pub supportedversions: Vec<Version>,
21    /// Required features
22    pub required_features: BTreeSet<String>,
23    /// Optional features
24    pub optional_features: BTreeSet<String>,
25    /// Client type identifier
26    pub client_type: String,
27    /// Client version
28    pub clientversion: Version,
29}
30
31impl ClientCapabilities {
32    /// Create new client capabilities
33    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    /// Set preferred version
45    pub fn with_preferred_version(mut self, version: Version) -> Self {
46        self.preferred_version = Some(version);
47        self
48    }
49
50    /// Add supported version
51    pub fn with_supported_version(mut self, version: Version) -> Self {
52        self.supportedversions.push(version);
53        self
54    }
55
56    /// Add required feature
57    pub fn require_feature(mut self, feature: &str) -> Self {
58        self.required_features.insert(feature.to_string());
59        self
60    }
61
62    /// Add optional feature
63    pub fn prefer_feature(mut self, feature: &str) -> Self {
64        self.optional_features.insert(feature.to_string());
65        self
66    }
67}
68
69/// Result of version negotiation
70#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
71#[derive(Debug, Clone)]
72pub struct NegotiationResult {
73    /// Negotiated version
74    pub negotiated_version: Version,
75    /// Available features in negotiated version
76    pub available_features: BTreeSet<String>,
77    /// Unsupported required features
78    pub unsupported_features: BTreeSet<String>,
79    /// Negotiation status
80    pub status: NegotiationStatus,
81    /// Negotiation metadata
82    pub metadata: NegotiationMetadata,
83}
84
85/// Status of version negotiation
86#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
87#[derive(Debug, Clone, Copy, PartialEq, Eq)]
88pub enum NegotiationStatus {
89    /// Negotiation successful
90    Success,
91    /// Negotiation successful with warnings
92    SuccessWithWarnings,
93    /// Partial success - some features unavailable
94    PartialSuccess,
95    /// Failed - no compatible version found
96    Failed,
97    /// Failed - required features not available
98    FeaturesMissing,
99}
100
101/// Metadata about the negotiation process
102#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
103#[derive(Debug, Clone)]
104pub struct NegotiationMetadata {
105    /// Versions considered during negotiation
106    pub consideredversions: Vec<Version>,
107    /// Reason for final selection
108    pub selection_reason: String,
109    /// Warnings generated during negotiation
110    pub warnings: Vec<String>,
111    /// Negotiation algorithm used
112    pub algorithm: String,
113}
114
115/// Version negotiation strategies
116#[derive(Debug, Clone, Copy, PartialEq, Eq)]
117pub enum NegotiationStrategy {
118    /// Prefer the highest compatible version
119    PreferLatest,
120    /// Prefer the client's preferred version if compatible
121    PreferClientPreference,
122    /// Prefer the most stable version
123    PreferStable,
124    /// Prefer the version with most features
125    PreferFeatureRich,
126    /// Custom negotiation logic
127    Custom,
128}
129
130/// Version negotiator implementation
131pub struct VersionNegotiator {
132    /// Default negotiation strategy
133    strategy: NegotiationStrategy,
134    /// Feature compatibility matrix
135    featurematrix: FeatureMatrix,
136}
137
138impl VersionNegotiator {
139    /// Create a new version negotiator
140    pub fn new() -> Self {
141        Self {
142            strategy: NegotiationStrategy::PreferLatest,
143            featurematrix: FeatureMatrix::new(),
144        }
145    }
146
147    /// Set negotiation strategy
148    pub fn with_strategy(mut self, strategy: NegotiationStrategy) -> Self {
149        self.strategy = strategy;
150        self
151    }
152
153    /// Negotiate version with client
154    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        // Find compatible versions
167        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        // Select best version based on strategy
181        let selected_version =
182            self.apply_strategy(&compatibleversions, client_capabilities, &mut metadata)?;
183
184        // Check feature compatibility
185        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    /// Find versions compatible with client
206    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            // Check if client supports this version
216            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    /// Select the best version based on strategy
233    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                // Fall back to latest
257                let mut versions = compatibleversions.to_vec();
258                versions.sort();
259                versions.reverse();
260                Ok(versions.into_iter().next().unwrap())
261            }
262            NegotiationStrategy::PreferStable => {
263                // Prefer non-pre-release versions
264                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                    // No stable versions, pick latest
277                    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                // This would require feature information for each version
285                // For now, fall back to latest
286                let mut versions = compatibleversions.to_vec();
287                versions.sort();
288                versions.reverse();
289                Ok(versions.into_iter().next().unwrap())
290            }
291            NegotiationStrategy::Custom => {
292                // Custom logic would be implemented here
293                let mut versions = compatibleversions.to_vec();
294                versions.sort();
295                versions.reverse();
296                Ok(versions.into_iter().next().unwrap())
297            }
298        }
299    }
300
301    /// Check feature compatibility
302    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
331/// Feature compatibility matrix
332struct FeatureMatrix {
333    /// Maps versions to their available features
334    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
357/// Client version requirements builder
358pub struct ClientRequirementsBuilder {
359    capabilities: ClientCapabilities,
360}
361
362impl ClientRequirementsBuilder {
363    /// Create a new builder
364    pub fn new(client_type: &str, clientversion: Version) -> Self {
365        Self {
366            capabilities: ClientCapabilities::new(client_type.to_string(), clientversion),
367        }
368    }
369
370    /// Set preferred version
371    pub fn preferred_version(mut self, version: Version) -> Self {
372        self.capabilities.preferred_version = Some(version);
373        self
374    }
375
376    /// Add supported version range
377    pub fn supportversions(mut self, versions: Vec<Version>) -> Self {
378        self.capabilities.supportedversions = versions;
379        self
380    }
381
382    /// Require a feature
383    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    /// Prefer a feature (optional)
391    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    /// Build the client capabilities
399    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        // Should prefer stable 1.1.0 over pre-release 2.0.0-alpha
496        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}