1use crate::api::{ApiVersion, VersionInfo};
4use crate::api::v3::ApiV3Client;
5use crate::api::v4::ApiV4Client as ApiV4ClientInner;
6use crate::Error;
7use log::debug;
8
9pub enum UnifiedClient {
11 V3(ApiV3Client),
12 V4(ApiV4ClientInner),
13}
14
15impl UnifiedClient {
16 pub async fn new(base_url: &str) -> Result<Self, Error> {
18 Self::with_version(base_url, None).await
19 }
20
21 pub async fn with_version(base_url: &str, version: Option<ApiVersion>) -> Result<Self, Error> {
23 let base_url = base_url.trim_end_matches('/');
24
25 match version {
26 Some(ApiVersion::V3) => {
27 debug!("Creating V3 client for {}", base_url);
28 Ok(UnifiedClient::V3(ApiV3Client::new(base_url)))
29 }
30 Some(ApiVersion::V4) => {
31 debug!("Creating V4 client for {}", base_url);
32 Ok(UnifiedClient::V4(ApiV4ClientInner::new(base_url)))
33 }
34 None => {
35 debug!("Auto-detecting API version for {}", base_url);
37 Self::detect_version(base_url).await
38 }
39 }
40 }
41
42 async fn detect_version(base_url: &str) -> Result<Self, Error> {
44 let base_url = base_url.trim_end_matches('/');
45
46 debug!("Trying V4 endpoint...");
48 let v4_client = ApiV4ClientInner::new(base_url);
49 match v4_client.ping().await {
50 Ok(_) => {
51 debug!("V4 endpoint available, using V4 client");
52 return Ok(UnifiedClient::V4(v4_client));
53 }
54 Err(e) => {
55 debug!("V4 endpoint failed: {}", e);
56 }
57 }
58
59 debug!("Trying V3 endpoint...");
61 let v3_client = ApiV3Client::new(base_url);
62 match v3_client.ping().await {
63 Ok(_) => {
64 debug!("V3 endpoint available, using V3 client");
65 Ok(UnifiedClient::V3(v3_client))
66 }
67 Err(e) => {
68 debug!("V3 endpoint failed: {}", e);
69 Err(Error::InvalidResponse(
70 "Could not detect API version. Neither V3 nor V4 endpoints responded.".to_string()
71 ))
72 }
73 }
74 }
75
76 pub async fn get_version(&self) -> Result<VersionInfo, Error> {
78 match self {
79 UnifiedClient::V3(client) => client.get_version().await,
80 UnifiedClient::V4(client) => client.get_version().await,
81 }
82 }
83
84 pub fn api_version(&self) -> ApiVersion {
86 match self {
87 UnifiedClient::V3(_) => ApiVersion::V3,
88 UnifiedClient::V4(_) => ApiVersion::V4,
89 }
90 }
91
92 pub fn base_url(&self) -> &str {
94 match self {
95 UnifiedClient::V3(client) => &client.base_url,
96 UnifiedClient::V4(client) => &client.base_url,
97 }
98 }
99
100 pub fn is_v3(&self) -> bool {
102 matches!(self, UnifiedClient::V3(_))
103 }
104
105 pub fn is_v4(&self) -> bool {
107 matches!(self, UnifiedClient::V4(_))
108 }
109
110 pub fn as_v3(&self) -> Option<&ApiV3Client> {
112 match self {
113 UnifiedClient::V3(client) => Some(client),
114 _ => None,
115 }
116 }
117
118 pub fn as_v4(&self) -> Option<&ApiV4ClientInner> {
120 match self {
121 UnifiedClient::V4(client) => Some(client),
122 _ => None,
123 }
124 }
125
126 pub fn as_v3_mut(&mut self) -> Option<&mut ApiV3Client> {
128 match self {
129 UnifiedClient::V3(client) => Some(client),
130 _ => None,
131 }
132 }
133
134 pub fn as_v4_mut(&mut self) -> Option<&mut ApiV4ClientInner> {
136 match self {
137 UnifiedClient::V4(client) => Some(client),
138 _ => None,
139 }
140 }
141}
142
143impl Clone for UnifiedClient {
145 fn clone(&self) -> Self {
146 match self {
147 UnifiedClient::V3(client) => UnifiedClient::V3(client.clone()),
148 UnifiedClient::V4(client) => UnifiedClient::V4(client.clone()),
149 }
150 }
151}
152
153impl std::fmt::Debug for UnifiedClient {
154 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
155 match self {
156 UnifiedClient::V3(client) => f
157 .debug_tuple("UnifiedClient::V3")
158 .field(client)
159 .finish(),
160 UnifiedClient::V4(client) => f
161 .debug_tuple("UnifiedClient::V4")
162 .field(client)
163 .finish(),
164 }
165 }
166}