1use serde::{Deserialize, Serialize};
19use thiserror::Error;
20
21use hessra_config::{HessraConfig, Protocol};
22
23#[derive(Error, Debug)]
25pub enum ApiError {
26 #[error("HTTP client error: {0}")]
27 HttpClient(#[from] reqwest::Error),
28
29 #[error("SSL configuration error: {0}")]
30 SslConfig(String),
31
32 #[error("Invalid response: {0}")]
33 InvalidResponse(String),
34
35 #[error("Token request error: {0}")]
36 TokenRequest(String),
37
38 #[error("Token verification error: {0}")]
39 TokenVerification(String),
40
41 #[error("Service chain error: {0}")]
42 ServiceChain(String),
43
44 #[error("Internal error: {0}")]
45 Internal(String),
46
47 #[error("Signoff failed: {0}")]
48 SignoffFailed(String),
49
50 #[error("Missing signoff configuration for service: {0}")]
51 MissingSignoffConfig(String),
52
53 #[error("Invalid signoff response from {service}: {reason}")]
54 InvalidSignoffResponse { service: String, reason: String },
55
56 #[error("Signoff collection incomplete: {missing_signoffs} signoffs remaining")]
57 IncompleteSignoffs { missing_signoffs: usize },
58}
59
60#[derive(Serialize, Deserialize)]
63pub struct TokenRequest {
64 pub resource: String,
66 pub operation: String,
68}
69
70#[derive(Serialize, Deserialize)]
72pub struct VerifyTokenRequest {
73 pub token: String,
75 pub subject: String,
77 pub resource: String,
79 pub operation: String,
81}
82
83#[derive(Serialize, Deserialize, Debug, Clone)]
85pub struct SignoffInfo {
86 pub component: String,
87 pub authorization_service: String,
88 pub public_key: String,
89}
90
91#[derive(Serialize, Deserialize, Debug, Clone)]
93pub struct SignTokenRequest {
94 pub token: String,
95 pub resource: String,
96 pub operation: String,
97}
98
99#[derive(Serialize, Deserialize, Debug, Clone)]
101pub struct SignTokenResponse {
102 pub response_msg: String,
103 pub signed_token: Option<String>,
104}
105
106#[derive(Serialize, Deserialize, Debug, Clone)]
108pub struct TokenResponse {
109 pub response_msg: String,
111 pub token: Option<String>,
113 #[serde(skip_serializing_if = "Option::is_none")]
115 pub pending_signoffs: Option<Vec<SignoffInfo>>,
116}
117
118#[derive(Serialize, Deserialize)]
120pub struct VerifyTokenResponse {
121 pub response_msg: String,
123}
124
125#[derive(Serialize, Deserialize)]
127pub struct PublicKeyResponse {
128 pub response_msg: String,
129 pub public_key: String,
130}
131
132#[derive(Serialize, Deserialize)]
134pub struct CaCertResponse {
135 pub response_msg: String,
136 pub ca_cert_pem: String,
137}
138
139#[derive(Serialize, Deserialize)]
141pub struct VerifyServiceChainTokenRequest {
142 pub token: String,
143 pub subject: String,
144 pub resource: String,
145 pub component: Option<String>,
146}
147
148#[derive(Serialize, Deserialize)]
150pub struct IdentityTokenRequest {
151 pub identifier: Option<String>,
153}
154
155#[derive(Serialize, Deserialize)]
157pub struct RefreshIdentityTokenRequest {
158 pub current_token: String,
160 pub identifier: Option<String>,
162}
163
164#[derive(Serialize, Deserialize, Debug, Clone)]
166pub struct IdentityTokenResponse {
167 pub response_msg: String,
169 pub token: Option<String>,
171 pub expires_in: Option<u64>,
173 pub identity: Option<String>,
175}
176
177#[derive(Clone)]
179pub struct BaseConfig {
180 pub base_url: String,
182 pub port: Option<u16>,
184 pub mtls_key: Option<String>,
186 pub mtls_cert: Option<String>,
188 pub server_ca: String,
190 pub public_key: Option<String>,
192 pub personal_keypair: Option<String>,
194}
195
196impl BaseConfig {
197 pub fn get_base_url(&self) -> String {
199 match self.port {
200 Some(port) => format!("{}:{port}", self.base_url),
201 None => self.base_url.clone(),
202 }
203 }
204}
205
206pub struct Http1Client {
208 config: BaseConfig,
210 client: reqwest::Client,
212}
213
214impl Http1Client {
215 pub fn new(config: BaseConfig) -> Result<Self, ApiError> {
217 let certs =
219 reqwest::Certificate::from_pem_bundle(config.server_ca.as_bytes()).map_err(|e| {
220 ApiError::SslConfig(format!("Failed to parse CA certificate chain: {e}"))
221 })?;
222
223 let mut client_builder = reqwest::ClientBuilder::new().use_rustls_tls();
225
226 for cert in certs {
228 client_builder = client_builder.add_root_certificate(cert);
229 }
230
231 if let (Some(cert), Some(key)) = (&config.mtls_cert, &config.mtls_key) {
233 let identity_str = format!("{cert}{key}");
234 let identity = reqwest::Identity::from_pem(identity_str.as_bytes()).map_err(|e| {
235 ApiError::SslConfig(format!(
236 "Failed to create identity from certificate and key: {e}"
237 ))
238 })?;
239 client_builder = client_builder.identity(identity);
240 }
241
242 let client = client_builder
243 .build()
244 .map_err(|e| ApiError::SslConfig(e.to_string()))?;
245
246 Ok(Self { config, client })
247 }
248
249 pub async fn send_request<T, R>(&self, endpoint: &str, request_body: &T) -> Result<R, ApiError>
251 where
252 T: Serialize,
253 R: for<'de> Deserialize<'de>,
254 {
255 let base_url = self.config.get_base_url();
256 let url = format!("https://{base_url}/{endpoint}");
257
258 let response = self
259 .client
260 .post(&url)
261 .json(request_body)
262 .send()
263 .await
264 .map_err(ApiError::HttpClient)?;
265
266 if !response.status().is_success() {
267 let status = response.status();
268 let error_text = response.text().await.unwrap_or_default();
269 return Err(ApiError::InvalidResponse(format!(
270 "HTTP error: {status} - {error_text}"
271 )));
272 }
273
274 let result = response
275 .json::<R>()
276 .await
277 .map_err(|e| ApiError::InvalidResponse(format!("Failed to parse response: {e}")))?;
278
279 Ok(result)
280 }
281
282 pub async fn send_request_with_auth<T, R>(
283 &self,
284 endpoint: &str,
285 request_body: &T,
286 auth_header: &str,
287 ) -> Result<R, ApiError>
288 where
289 T: Serialize,
290 R: for<'de> Deserialize<'de>,
291 {
292 let base_url = self.config.get_base_url();
293 let url = format!("https://{base_url}/{endpoint}");
294
295 let response = self
296 .client
297 .post(&url)
298 .header("Authorization", auth_header)
299 .json(request_body)
300 .send()
301 .await
302 .map_err(ApiError::HttpClient)?;
303
304 if !response.status().is_success() {
305 let status = response.status();
306 let error_text = response.text().await.unwrap_or_default();
307 return Err(ApiError::InvalidResponse(format!(
308 "HTTP error: {status} - {error_text}"
309 )));
310 }
311
312 let result = response
313 .json::<R>()
314 .await
315 .map_err(|e| ApiError::InvalidResponse(format!("Failed to parse response: {e}")))?;
316
317 Ok(result)
318 }
319}
320
321#[cfg(feature = "http3")]
323pub struct Http3Client {
324 config: BaseConfig,
326 client: reqwest::Client,
328}
329
330#[cfg(feature = "http3")]
331impl Http3Client {
332 pub fn new(config: BaseConfig) -> Result<Self, ApiError> {
334 let certs =
336 reqwest::Certificate::from_pem_bundle(config.server_ca.as_bytes()).map_err(|e| {
337 ApiError::SslConfig(format!("Failed to parse CA certificate chain: {e}"))
338 })?;
339
340 let mut client_builder = reqwest::ClientBuilder::new()
342 .use_rustls_tls()
343 .http3_prior_knowledge();
344
345 for cert in certs {
347 client_builder = client_builder.add_root_certificate(cert);
348 }
349
350 if let (Some(cert), Some(key)) = (&config.mtls_cert, &config.mtls_key) {
352 let identity_str = format!("{}{}", cert, key);
353 let identity = reqwest::Identity::from_pem(identity_str.as_bytes()).map_err(|e| {
354 ApiError::SslConfig(format!(
355 "Failed to create identity from certificate and key: {e}"
356 ))
357 })?;
358 client_builder = client_builder.identity(identity);
359 }
360
361 let client = client_builder
362 .build()
363 .map_err(|e| ApiError::SslConfig(e.to_string()))?;
364
365 Ok(Self { config, client })
366 }
367
368 pub async fn send_request<T, R>(&self, endpoint: &str, request_body: &T) -> Result<R, ApiError>
370 where
371 T: Serialize,
372 R: for<'de> Deserialize<'de>,
373 {
374 let base_url = self.config.get_base_url();
375 let url = format!("https://{base_url}/{endpoint}");
376
377 let response = self
378 .client
379 .post(&url)
380 .json(request_body)
381 .send()
382 .await
383 .map_err(ApiError::HttpClient)?;
384
385 if !response.status().is_success() {
386 let status = response.status();
387 let error_text = response.text().await.unwrap_or_default();
388 return Err(ApiError::InvalidResponse(format!(
389 "HTTP error: {status} - {error_text}"
390 )));
391 }
392
393 let result = response
394 .json::<R>()
395 .await
396 .map_err(|e| ApiError::InvalidResponse(format!("Failed to parse response: {e}")))?;
397
398 Ok(result)
399 }
400
401 pub async fn send_request_with_auth<T, R>(
402 &self,
403 endpoint: &str,
404 request_body: &T,
405 auth_header: &str,
406 ) -> Result<R, ApiError>
407 where
408 T: Serialize,
409 R: for<'de> Deserialize<'de>,
410 {
411 let base_url = self.config.get_base_url();
412 let url = format!("https://{base_url}/{endpoint}");
413
414 let response = self
415 .client
416 .post(&url)
417 .header("Authorization", auth_header)
418 .json(request_body)
419 .send()
420 .await
421 .map_err(ApiError::HttpClient)?;
422
423 if !response.status().is_success() {
424 let status = response.status();
425 let error_text = response.text().await.unwrap_or_default();
426 return Err(ApiError::InvalidResponse(format!(
427 "HTTP error: {status} - {error_text}"
428 )));
429 }
430
431 let result = response
432 .json::<R>()
433 .await
434 .map_err(|e| ApiError::InvalidResponse(format!("Failed to parse response: {e}")))?;
435
436 Ok(result)
437 }
438}
439
440pub enum HessraClient {
442 Http1(Http1Client),
444 #[cfg(feature = "http3")]
446 Http3(Http3Client),
447}
448
449pub struct HessraClientBuilder {
451 config: BaseConfig,
453 protocol: hessra_config::Protocol,
455}
456
457impl HessraClientBuilder {
458 pub fn new() -> Self {
460 Self {
461 config: BaseConfig {
462 base_url: String::new(),
463 port: None,
464 mtls_key: None,
465 mtls_cert: None,
466 server_ca: String::new(),
467 public_key: None,
468 personal_keypair: None,
469 },
470 protocol: Protocol::Http1,
471 }
472 }
473
474 pub fn from_config(mut self, config: &HessraConfig) -> Self {
476 self.config.base_url = config.base_url.clone();
477 self.config.port = config.port;
478 self.config.mtls_key = config.mtls_key.clone();
479 self.config.mtls_cert = config.mtls_cert.clone();
480 self.config.server_ca = config.server_ca.clone();
481 self.config.public_key = config.public_key.clone();
482 self.config.personal_keypair = config.personal_keypair.clone();
483 self.protocol = config.protocol.clone();
484 self
485 }
486
487 pub fn base_url(mut self, base_url: impl Into<String>) -> Self {
489 self.config.base_url = base_url.into();
490 self
491 }
492
493 pub fn mtls_key(mut self, mtls_key: impl Into<String>) -> Self {
496 self.config.mtls_key = Some(mtls_key.into());
497 self
498 }
499
500 pub fn mtls_cert(mut self, mtls_cert: impl Into<String>) -> Self {
503 self.config.mtls_cert = Some(mtls_cert.into());
504 self
505 }
506
507 pub fn server_ca(mut self, server_ca: impl Into<String>) -> Self {
510 self.config.server_ca = server_ca.into();
511 self
512 }
513
514 pub fn port(mut self, port: u16) -> Self {
516 self.config.port = Some(port);
517 self
518 }
519
520 pub fn protocol(mut self, protocol: Protocol) -> Self {
522 self.protocol = protocol;
523 self
524 }
525
526 pub fn public_key(mut self, public_key: impl Into<String>) -> Self {
529 self.config.public_key = Some(public_key.into());
530 self
531 }
532
533 pub fn personal_keypair(mut self, keypair: impl Into<String>) -> Self {
537 self.config.personal_keypair = Some(keypair.into());
538 self
539 }
540
541 fn build_http1(&self) -> Result<Http1Client, ApiError> {
543 Http1Client::new(self.config.clone())
544 }
545
546 #[cfg(feature = "http3")]
548 fn build_http3(&self) -> Result<Http3Client, ApiError> {
549 Http3Client::new(self.config.clone())
550 }
551
552 pub fn build(self) -> Result<HessraClient, ApiError> {
554 match self.protocol {
555 Protocol::Http1 => Ok(HessraClient::Http1(self.build_http1()?)),
556 #[cfg(feature = "http3")]
557 Protocol::Http3 => Ok(HessraClient::Http3(self.build_http3()?)),
558 #[allow(unreachable_patterns)]
559 _ => Err(ApiError::Internal("Unsupported protocol".to_string())),
560 }
561 }
562}
563
564impl Default for HessraClientBuilder {
565 fn default() -> Self {
566 Self::new()
567 }
568}
569
570impl HessraClient {
571 pub fn builder() -> HessraClientBuilder {
573 HessraClientBuilder::new()
574 }
575
576 pub async fn fetch_public_key(
580 base_url: impl Into<String>,
581 port: Option<u16>,
582 server_ca: impl Into<String>,
583 ) -> Result<String, ApiError> {
584 let base_url = base_url.into();
585 let server_ca = server_ca.into();
586
587 let cert_pem = server_ca.as_bytes();
589 let certs = reqwest::Certificate::from_pem_bundle(cert_pem)
590 .map_err(|e| ApiError::SslConfig(e.to_string()))?;
591
592 let mut client_builder = reqwest::ClientBuilder::new().use_rustls_tls();
593 for cert in certs {
594 client_builder = client_builder.add_root_certificate(cert);
595 }
596
597 let client = client_builder
598 .build()
599 .map_err(|e| ApiError::SslConfig(e.to_string()))?;
600
601 let url = match port {
603 Some(port) => format!("https://{base_url}:{port}/public_key"),
604 None => format!("https://{base_url}/public_key"),
605 };
606
607 let response = client
609 .get(&url)
610 .send()
611 .await
612 .map_err(ApiError::HttpClient)?;
613
614 if !response.status().is_success() {
615 let status = response.status();
616 let error_text = response.text().await.unwrap_or_default();
617 return Err(ApiError::InvalidResponse(format!(
618 "HTTP error: {status} - {error_text}"
619 )));
620 }
621
622 let result = response
624 .json::<PublicKeyResponse>()
625 .await
626 .map_err(|e| ApiError::InvalidResponse(format!("Failed to parse response: {e}")))?;
627
628 Ok(result.public_key)
629 }
630
631 pub async fn fetch_ca_cert(
643 base_url: impl Into<String>,
644 port: Option<u16>,
645 ) -> Result<String, ApiError> {
646 let base_url = base_url.into();
647
648 let client = reqwest::ClientBuilder::new()
650 .use_rustls_tls()
651 .build()
652 .map_err(|e| ApiError::SslConfig(e.to_string()))?;
653
654 let url = match port {
656 Some(port) => format!("https://{base_url}:{port}/ca_cert"),
657 None => format!("https://{base_url}/ca_cert"),
658 };
659
660 let response = client
662 .get(&url)
663 .send()
664 .await
665 .map_err(ApiError::HttpClient)?;
666
667 if !response.status().is_success() {
668 let status = response.status();
669 let error_text = response.text().await.unwrap_or_default();
670 return Err(ApiError::InvalidResponse(format!(
671 "HTTP error: {status} - {error_text}"
672 )));
673 }
674
675 let result = response
677 .json::<CaCertResponse>()
678 .await
679 .map_err(|e| ApiError::InvalidResponse(format!("Failed to parse response: {e}")))?;
680
681 if result.ca_cert_pem.is_empty() {
683 return Err(ApiError::InvalidResponse(
684 "Server returned empty CA certificate".to_string(),
685 ));
686 }
687
688 if !result.ca_cert_pem.contains("-----BEGIN CERTIFICATE-----") {
689 return Err(ApiError::InvalidResponse(
690 "Server returned invalid PEM format".to_string(),
691 ));
692 }
693
694 Ok(result.ca_cert_pem)
695 }
696
697 #[cfg(feature = "http3")]
698 pub async fn fetch_public_key_http3(
699 base_url: impl Into<String>,
700 port: Option<u16>,
701 server_ca: impl Into<String>,
702 ) -> Result<String, ApiError> {
703 let base_url = base_url.into();
704 let server_ca = server_ca.into();
705
706 let cert_pem = server_ca.as_bytes();
708 let certs = reqwest::Certificate::from_pem_bundle(cert_pem)
709 .map_err(|e| ApiError::SslConfig(e.to_string()))?;
710
711 let mut client_builder = reqwest::ClientBuilder::new()
712 .use_rustls_tls()
713 .http3_prior_knowledge();
714 for cert in certs {
715 client_builder = client_builder.add_root_certificate(cert);
716 }
717
718 let client = client_builder
719 .build()
720 .map_err(|e| ApiError::SslConfig(e.to_string()))?;
721
722 let url = match port {
724 Some(port) => format!("https://{base_url}:{port}/public_key"),
725 None => format!("https://{base_url}/public_key"),
726 };
727
728 let response = client
730 .get(&url)
731 .send()
732 .await
733 .map_err(ApiError::HttpClient)?;
734
735 if !response.status().is_success() {
736 let status = response.status();
737 let error_text = response.text().await.unwrap_or_default();
738 return Err(ApiError::InvalidResponse(format!(
739 "HTTP error: {status} - {error_text}"
740 )));
741 }
742
743 let result = response
745 .json::<PublicKeyResponse>()
746 .await
747 .map_err(|e| ApiError::InvalidResponse(format!("Failed to parse response: {e}")))?;
748
749 Ok(result.public_key)
750 }
751
752 pub async fn request_token(
755 &self,
756 resource: String,
757 operation: String,
758 ) -> Result<TokenResponse, ApiError> {
759 let request = TokenRequest {
760 resource,
761 operation,
762 };
763
764 let response = match self {
765 HessraClient::Http1(client) => {
766 client
767 .send_request::<_, TokenResponse>("request_token", &request)
768 .await?
769 }
770 #[cfg(feature = "http3")]
771 HessraClient::Http3(client) => {
772 client
773 .send_request::<_, TokenResponse>("request_token", &request)
774 .await?
775 }
776 };
777
778 Ok(response)
779 }
780
781 pub async fn request_token_with_identity(
785 &self,
786 resource: String,
787 operation: String,
788 identity_token: String,
789 ) -> Result<TokenResponse, ApiError> {
790 let request = TokenRequest {
791 resource,
792 operation,
793 };
794
795 let response = match self {
796 HessraClient::Http1(client) => {
797 client
798 .send_request_with_auth::<_, TokenResponse>(
799 "request_token",
800 &request,
801 &format!("Bearer {identity_token}"),
802 )
803 .await?
804 }
805 #[cfg(feature = "http3")]
806 HessraClient::Http3(client) => {
807 client
808 .send_request_with_auth::<_, TokenResponse>(
809 "request_token",
810 &request,
811 &format!("Bearer {identity_token}"),
812 )
813 .await?
814 }
815 };
816
817 Ok(response)
818 }
819
820 pub async fn request_token_simple(
823 &self,
824 resource: String,
825 operation: String,
826 ) -> Result<String, ApiError> {
827 let response = self.request_token(resource, operation).await?;
828
829 match response.token {
830 Some(token) => Ok(token),
831 None => Err(ApiError::TokenRequest(format!(
832 "Failed to get token: {}",
833 response.response_msg
834 ))),
835 }
836 }
837
838 pub async fn verify_token(
841 &self,
842 token: String,
843 subject: String,
844 resource: String,
845 operation: String,
846 ) -> Result<String, ApiError> {
847 let request = VerifyTokenRequest {
848 token,
849 subject,
850 resource,
851 operation,
852 };
853
854 let response = match self {
855 HessraClient::Http1(client) => {
856 client
857 .send_request::<_, VerifyTokenResponse>("verify_token", &request)
858 .await?
859 }
860 #[cfg(feature = "http3")]
861 HessraClient::Http3(client) => {
862 client
863 .send_request::<_, VerifyTokenResponse>("verify_token", &request)
864 .await?
865 }
866 };
867
868 Ok(response.response_msg)
869 }
870
871 pub async fn verify_service_chain_token(
878 &self,
879 token: String,
880 subject: String,
881 resource: String,
882 component: Option<String>,
883 ) -> Result<String, ApiError> {
884 let request = VerifyServiceChainTokenRequest {
885 token,
886 subject,
887 resource,
888 component,
889 };
890
891 let response = match self {
892 HessraClient::Http1(client) => {
893 client
894 .send_request::<_, VerifyTokenResponse>("verify_service_chain_token", &request)
895 .await?
896 }
897 #[cfg(feature = "http3")]
898 HessraClient::Http3(client) => {
899 client
900 .send_request::<_, VerifyTokenResponse>("verify_service_chain_token", &request)
901 .await?
902 }
903 };
904
905 Ok(response.response_msg)
906 }
907
908 pub async fn sign_token(
910 &self,
911 token: &str,
912 resource: &str,
913 operation: &str,
914 ) -> Result<SignTokenResponse, ApiError> {
915 let request = SignTokenRequest {
916 token: token.to_string(),
917 resource: resource.to_string(),
918 operation: operation.to_string(),
919 };
920
921 let response = match self {
922 HessraClient::Http1(client) => {
923 client
924 .send_request::<_, SignTokenResponse>("sign_token", &request)
925 .await?
926 }
927 #[cfg(feature = "http3")]
928 HessraClient::Http3(client) => {
929 client
930 .send_request::<_, SignTokenResponse>("sign_token", &request)
931 .await?
932 }
933 };
934
935 Ok(response)
936 }
937
938 pub async fn get_public_key(&self) -> Result<String, ApiError> {
940 let url_path = "public_key";
941
942 let response = match self {
943 HessraClient::Http1(client) => {
944 let base_url = client.config.get_base_url();
946 let full_url = format!("https://{base_url}/{url_path}");
947
948 let response = client
949 .client
950 .get(&full_url)
951 .send()
952 .await
953 .map_err(ApiError::HttpClient)?;
954
955 if !response.status().is_success() {
956 let status = response.status();
957 let error_text = response.text().await.unwrap_or_default();
958 return Err(ApiError::InvalidResponse(format!(
959 "HTTP error: {status} - {error_text}"
960 )));
961 }
962
963 response.json::<PublicKeyResponse>().await.map_err(|e| {
964 ApiError::InvalidResponse(format!("Failed to parse response: {e}"))
965 })?
966 }
967 #[cfg(feature = "http3")]
968 HessraClient::Http3(client) => {
969 let base_url = client.config.get_base_url();
970 let full_url = format!("https://{base_url}/{url_path}");
971
972 let response = client
973 .client
974 .get(&full_url)
975 .send()
976 .await
977 .map_err(ApiError::HttpClient)?;
978
979 if !response.status().is_success() {
980 let status = response.status();
981 let error_text = response.text().await.unwrap_or_default();
982 return Err(ApiError::InvalidResponse(format!(
983 "HTTP error: {status} - {error_text}"
984 )));
985 }
986
987 response.json::<PublicKeyResponse>().await.map_err(|e| {
988 ApiError::InvalidResponse(format!("Failed to parse response: {e}"))
989 })?
990 }
991 };
992
993 Ok(response.public_key)
994 }
995
996 pub async fn request_identity_token(
1004 &self,
1005 identifier: Option<String>,
1006 ) -> Result<IdentityTokenResponse, ApiError> {
1007 let request = IdentityTokenRequest { identifier };
1008
1009 let response = match self {
1010 HessraClient::Http1(client) => {
1011 client
1012 .send_request::<_, IdentityTokenResponse>("request_identity_token", &request)
1013 .await?
1014 }
1015 #[cfg(feature = "http3")]
1016 HessraClient::Http3(client) => {
1017 client
1018 .send_request::<_, IdentityTokenResponse>("request_identity_token", &request)
1019 .await?
1020 }
1021 };
1022
1023 Ok(response)
1024 }
1025
1026 pub async fn refresh_identity_token(
1036 &self,
1037 current_token: String,
1038 identifier: Option<String>,
1039 ) -> Result<IdentityTokenResponse, ApiError> {
1040 let request = RefreshIdentityTokenRequest {
1041 current_token,
1042 identifier,
1043 };
1044
1045 let response = match self {
1046 HessraClient::Http1(client) => {
1047 client
1048 .send_request::<_, IdentityTokenResponse>("refresh_identity_token", &request)
1049 .await?
1050 }
1051 #[cfg(feature = "http3")]
1052 HessraClient::Http3(client) => {
1053 client
1054 .send_request::<_, IdentityTokenResponse>("refresh_identity_token", &request)
1055 .await?
1056 }
1057 };
1058
1059 Ok(response)
1060 }
1061}
1062
1063#[cfg(test)]
1064mod tests {
1065 use super::*;
1066
1067 #[test]
1069 fn test_base_config_get_base_url_with_port() {
1070 let config = BaseConfig {
1071 base_url: "test.hessra.net".to_string(),
1072 port: Some(443),
1073 mtls_key: None,
1074 mtls_cert: None,
1075 server_ca: "".to_string(),
1076 public_key: None,
1077 personal_keypair: None,
1078 };
1079
1080 assert_eq!(config.get_base_url(), "test.hessra.net:443");
1081 }
1082
1083 #[test]
1084 fn test_base_config_get_base_url_without_port() {
1085 let config = BaseConfig {
1086 base_url: "test.hessra.net".to_string(),
1087 port: None,
1088 mtls_key: None,
1089 mtls_cert: None,
1090 server_ca: "".to_string(),
1091 public_key: None,
1092 personal_keypair: None,
1093 };
1094
1095 assert_eq!(config.get_base_url(), "test.hessra.net");
1096 }
1097
1098 #[test]
1100 fn test_client_builder_methods() {
1101 let builder = HessraClientBuilder::new()
1102 .base_url("test.hessra.net")
1103 .port(443)
1104 .protocol(Protocol::Http1)
1105 .mtls_cert("CERT")
1106 .mtls_key("KEY")
1107 .server_ca("CA")
1108 .public_key("PUBKEY")
1109 .personal_keypair("KEYPAIR");
1110
1111 assert_eq!(builder.config.base_url, "test.hessra.net");
1112 assert_eq!(builder.config.port, Some(443));
1113 assert_eq!(builder.config.mtls_cert, Some("CERT".to_string()));
1114 assert_eq!(builder.config.mtls_key, Some("KEY".to_string()));
1115 assert_eq!(builder.config.server_ca, "CA");
1116 assert_eq!(builder.config.public_key, Some("PUBKEY".to_string()));
1117 assert_eq!(builder.config.personal_keypair, Some("KEYPAIR".to_string()));
1118 }
1119}