// rust/02-api-versioning.rs/*API versioning patterns and best practices*/useserde::{Deserialize,Serialize};usestd::collections::HashMap;/// API version enumeration.#[derive(Debug, Clone, PartialEq, Eq, Hash)]pubenumApiVersion{V1,V2,V3,}implApiVersion{pubfnfrom_str(s:&str)->Option<Self>{matchs{"v1"|"1"=>Some(ApiVersion::V1),"v2"|"2"=>Some(ApiVersion::V2),"v3"|"3"=>Some(ApiVersion::V3),_=>None,}}pubfnto_string(&self)->String{matchself{ApiVersion::V1=>"v1".to_string(),ApiVersion::V2=>"v2".to_string(),ApiVersion::V3=>"v3".to_string(),}}}/// Versioned user structure for V1.#[derive(Debug, Clone, Serialize, Deserialize)]pubstructUserV1{pubid:u64,pubname:String,pubemail:String,}/// Versioned user structure for V2.#[derive(Debug, Clone, Serialize, Deserialize)]pubstructUserV2{pubid:u64,pubname:String,pubemail:String,pubcreated_at:chrono::DateTime<chrono::Utc>,}/// Versioned user structure for V3.#[derive(Debug, Clone, Serialize, Deserialize)]pubstructUserV3{pubid:u64,pubname:String,pubemail:String,pubcreated_at:chrono::DateTime<chrono::Utc>,pubupdated_at:chrono::DateTime<chrono::Utc>,pubmetadata:HashMap<String,String>,}/// Versioned API response.#[derive(Debug, Serialize)]pubstructVersionedResponse<T>{pubdata:T,pubversion:String,pubapi_version:String,}/// Versioned API service.pubstructVersionedApiService{users:HashMap<u64,UserV3>,next_id:u64,}implVersionedApiService{pubfnnew()->Self{Self{users:HashMap::new(),next_id:1,}}pubfncreate_user(&mutself,name:String,email:String)->Result<UserV3,String>{letnow=chrono::Utc::now();letuser=UserV3{id:self.next_id,name,email,created_at:now,updated_at:now,metadata:HashMap::new(),};self.next_id+=1;self.users.insert(user.id,user.clone());Ok(user)}pubfnget_user(&self,id:u64)->Option<&UserV3>{self.users.get(&id)}pubfnget_user_v1(&self,id:u64)->Option<UserV1>{self.users.get(&id).map(|u|UserV1{id:u.id,name:u.name.clone(),email:u.email.clone(),})}pubfnget_user_v2(&self,id:u64)->Option<UserV2>{self.users.get(&id).map(|u|UserV2{id:u.id,name:u.name.clone(),email:u.email.clone(),created_at:u.created_at,})}pubfnget_user_v3(&self,id:u64)->Option<&UserV3>{self.users.get(&id)}pubfnlist_users(&self,version:ApiVersion)->Vec<Box<dynserde::Serialize>>{matchversion{ApiVersion::V1=>{letusers:Vec<UserV1>=self.users.values().map(|u|UserV1{id:u.id,name:u.name.clone(),email:u.email.clone(),}).collect();users.into_iter().map(|u|Box::new(u)asBox<dynserde::Serialize>).collect()}ApiVersion::V2=>{letusers:Vec<UserV2>=self.users.values().map(|u|UserV2{id:u.id,name:u.name.clone(),email:u.email.clone(),created_at:u.created_at,}).collect();users.into_iter().map(|u|Box::new(u)asBox<dynserde::Serialize>).collect()}ApiVersion::V3=>{letusers:Vec<&UserV3>=self.users.values().collect();users.into_iter().map(|u|Box::new(*u)asBox<dynserde::Serialize>).collect()}}}}/// API version middleware.pubstructApiVersionMiddleware{default_version:ApiVersion,supported_versions:Vec<ApiVersion>,}implApiVersionMiddleware{pubfnnew(default_version:ApiVersion)->Self{Self{default_version,supported_versions:vec![ApiVersion::V1,ApiVersion::V2,ApiVersion::V3],}}pubfnextract_version(&self,path:&str,header:Option<&str>)->ApiVersion{// Try to extract version from pathifletSome(version_str)=path.split('/').nth(1){ifletSome(version)=ApiVersion::from_str(version_str){ifself.supported_versions.contains(&version){returnversion;}}}// Try to extract version from headerifletSome(header)=header{ifletSome(version_str)=header.split('=').nth(1){ifletSome(version)=ApiVersion::from_str(version_str){ifself.supported_versions.contains(&version){returnversion;}}}}// Return default versionself.default_version.clone()}pubfnis_version_supported(&self,version:&ApiVersion)->bool{self.supported_versions.contains(version)}}#[cfg(test)]modtests{usesuper::*;#[test]fntest_api_version_parsing(){assert_eq!(ApiVersion::from_str("v1"),Some(ApiVersion::V1));assert_eq!(ApiVersion::from_str("v2"),Some(ApiVersion::V2));assert_eq!(ApiVersion::from_str("v3"),Some(ApiVersion::V3));assert_eq!(ApiVersion::from_str("invalid"),None);}#[test]fntest_versioned_api_service(){letmutservice=VersionedApiService::new();letuser=service.create_user("John".to_string(),"john@example.com".to_string()).unwrap();assert_eq!(user.name,"John");assert_eq!(user.email,"john@example.com");letuser_v1=service.get_user_v1(user.id).unwrap();assert_eq!(user_v1.name,"John");letuser_v2=service.get_user_v2(user.id).unwrap();assert_eq!(user_v2.name,"John");letuser_v3=service.get_user_v3(user.id).unwrap();assert_eq!(user_v3.name,"John");}#[test]fntest_api_version_middleware(){letmiddleware=ApiVersionMiddleware::new(ApiVersion::V1);letversion=middleware.extract_version("/api/v2/users",None);assert_eq!(version,ApiVersion::V2);letversion=middleware.extract_version("/api/invalid/users",None);assert_eq!(version,ApiVersion::V1);assert!(middleware.is_version_supported(&ApiVersion::V1));assert!(middleware.is_version_supported(&ApiVersion::V2));assert!(middleware.is_version_supported(&ApiVersion::V3));}}