1use std::fs::File;
32use std::io::Read;
33use std::path::Path;
34use thiserror::Error;
35
36pub use hessra_token::{
38 add_service_node_attestation,
40 decode_token,
41 encode_token,
42 add_prefix_restriction,
44 add_prefix_restriction_to_token,
45 verify_biscuit_local,
47 verify_service_chain_biscuit_local,
48 AuthorizationVerifier,
50 Biscuit,
52 KeyPair,
53 PublicKey,
54 ServiceNode,
56 TokenError,
58};
59
60pub use hessra_token_identity::{
62 add_identity_attenuation_to_token, create_identity_token, create_short_lived_identity_token,
63 verify_identity_token,
64};
65
66pub use hessra_config::{ConfigError, HessraConfig, Protocol};
67
68pub use hessra_api::{
69 parse_server_address, ApiError, HessraClient, HessraClientBuilder, IdentityTokenRequest,
70 IdentityTokenResponse, MintIdentityTokenRequest, MintIdentityTokenResponse, PublicKeyResponse,
71 RefreshIdentityTokenRequest, SignTokenRequest, SignTokenResponse, SignoffInfo,
72 StubTokenRequest, StubTokenResponse, TokenRequest, TokenResponse,
73 VerifyServiceChainTokenRequest, VerifyTokenRequest, VerifyTokenResponse,
74};
75
76#[derive(Error, Debug)]
78pub enum SdkError {
79 #[error("Configuration error: {0}")]
81 Config(#[from] ConfigError),
82
83 #[error("API error: {0}")]
85 Api(#[from] ApiError),
86
87 #[error("Token error: {0}")]
89 Token(#[from] TokenError),
90
91 #[error("JSON error: {0}")]
93 Json(#[from] serde_json::Error),
94
95 #[error("I/O error: {0}")]
97 Io(#[from] std::io::Error),
98
99 #[error("{0}")]
101 Generic(String),
102}
103
104#[derive(Clone, Debug, Default)]
110pub struct ServiceChain {
111 nodes: Vec<ServiceNode>,
113}
114
115impl ServiceChain {
116 pub fn new() -> Self {
118 Self { nodes: Vec::new() }
119 }
120
121 pub fn with_nodes(nodes: Vec<ServiceNode>) -> Self {
123 Self { nodes }
124 }
125
126 pub fn builder() -> ServiceChainBuilder {
128 ServiceChainBuilder::new()
129 }
130
131 pub fn add_node(&mut self, node: ServiceNode) -> &mut Self {
133 self.nodes.push(node);
134 self
135 }
136
137 pub fn with_node(mut self, node: ServiceNode) -> Self {
139 self.nodes.push(node);
140 self
141 }
142
143 pub fn nodes(&self) -> &[ServiceNode] {
145 &self.nodes
146 }
147
148 fn to_internal(&self) -> Vec<hessra_token::ServiceNode> {
150 self.nodes.to_vec()
151 }
152
153 pub fn from_json(json: &str) -> Result<Self, SdkError> {
155 let nodes: Vec<ServiceNode> = serde_json::from_str(json)?;
156 Ok(Self::with_nodes(nodes))
157 }
158
159 pub fn from_json_file(path: impl AsRef<Path>) -> Result<Self, SdkError> {
161 let mut file = File::open(path)?;
162 let mut contents = String::new();
163 file.read_to_string(&mut contents)?;
164 Self::from_json(&contents)
165 }
166
167 #[cfg(feature = "toml")]
169 pub fn from_toml(toml_str: &str) -> Result<Self, SdkError> {
170 use serde::Deserialize;
171
172 #[derive(Deserialize)]
173 struct TomlServiceChain {
174 nodes: Vec<ServiceNode>,
175 }
176
177 let chain: TomlServiceChain = toml::from_str(toml_str)
178 .map_err(|e| SdkError::Generic(format!("TOML parse error: {e}")))?;
179
180 Ok(Self::with_nodes(chain.nodes))
181 }
182
183 #[cfg(feature = "toml")]
185 pub fn from_toml_file(path: impl AsRef<Path>) -> Result<Self, SdkError> {
186 let mut file = File::open(path)?;
187 let mut contents = String::new();
188 file.read_to_string(&mut contents)?;
189 Self::from_toml(&contents)
190 }
191}
192
193#[derive(Debug, Default)]
195pub struct ServiceChainBuilder {
196 nodes: Vec<ServiceNode>,
197}
198
199impl ServiceChainBuilder {
200 pub fn new() -> Self {
202 Self::default()
203 }
204
205 pub fn add_node(mut self, node: ServiceNode) -> Self {
207 self.nodes.push(node);
208 self
209 }
210
211 pub fn build(self) -> ServiceChain {
213 ServiceChain::with_nodes(self.nodes)
214 }
215}
216
217pub struct Hessra {
222 client: HessraClient,
223 config: HessraConfig,
224}
225
226impl Hessra {
227 pub fn new(config: HessraConfig) -> Result<Self, SdkError> {
229 let client = HessraClientBuilder::new()
230 .from_config(&config)
231 .build()
232 .map_err(|e| SdkError::Generic(e.to_string()))?;
233
234 Ok(Self { client, config })
235 }
236
237 pub fn builder() -> HessraBuilder {
239 HessraBuilder::new()
240 }
241
242 pub async fn setup(&mut self) -> Result<(), SdkError> {
248 match self.get_public_key().await {
249 Ok(public_key) => {
250 self.config.public_key = Some(public_key);
251 Ok(())
252 }
253 Err(e) => Err(SdkError::Generic(e.to_string())),
254 }
255 }
256
257 pub async fn with_setup(&self) -> Result<Self, SdkError> {
262 match self.get_public_key().await {
263 Ok(public_key) => {
264 let config = self.config.to_builder().public_key(public_key).build()?;
265 Ok(Self::new(config)?)
266 }
267 Err(e) => Err(SdkError::Generic(e.to_string())),
268 }
269 }
270
271 pub async fn request_token(
279 &self,
280 resource: impl Into<String>,
281 operation: impl Into<String>,
282 domain: Option<String>,
283 ) -> Result<TokenResponse, SdkError> {
284 self.client
285 .request_token(resource.into(), operation.into(), domain)
286 .await
287 .map_err(|e| SdkError::Generic(e.to_string()))
288 }
289
290 fn apply_jit_attenuation(&self, identity_token: String) -> String {
293 if let Some(ref public_key_pem) = self.config.public_key {
295 if let Ok(public_key) = PublicKey::from_pem(public_key_pem.as_str()) {
297 if let Ok(attenuated_token) =
299 create_short_lived_identity_token(identity_token.clone(), public_key)
300 {
301 return attenuated_token;
302 }
303 }
304 }
305 identity_token
307 }
308
309 pub async fn request_token_with_identity(
320 &self,
321 resource: impl Into<String>,
322 operation: impl Into<String>,
323 identity_token: impl Into<String>,
324 domain: Option<String>,
325 ) -> Result<TokenResponse, SdkError> {
326 let token = identity_token.into();
327 let attenuated_token = self.apply_jit_attenuation(token);
329
330 self.client
331 .request_token_with_identity(
332 resource.into(),
333 operation.into(),
334 attenuated_token,
335 domain,
336 )
337 .await
338 .map_err(|e| SdkError::Generic(e.to_string()))
339 }
340
341 pub async fn request_token_simple(
344 &self,
345 resource: impl Into<String>,
346 operation: impl Into<String>,
347 ) -> Result<String, SdkError> {
348 let response = self.request_token(resource, operation, None).await?;
349 match response.token {
350 Some(token) => Ok(token),
351 None => Err(SdkError::Generic(format!(
352 "Failed to get token: {}",
353 response.response_msg
354 ))),
355 }
356 }
357
358 pub async fn sign_token(
360 &self,
361 token: &str,
362 resource: &str,
363 operation: &str,
364 ) -> Result<SignTokenResponse, SdkError> {
365 self.client
366 .sign_token(token, resource, operation)
367 .await
368 .map_err(|e| SdkError::Generic(e.to_string()))
369 }
370
371 fn parse_authorization_service_url(url: &str) -> Result<(String, Option<u16>), SdkError> {
375 let url_str = if url.starts_with("http://") || url.starts_with("https://") {
376 url.to_string()
377 } else {
378 format!("https://{url}")
380 };
381
382 let parsed_url = url::Url::parse(&url_str).map_err(|e| {
383 SdkError::Generic(format!(
384 "Failed to parse authorization service URL '{url}': {e}"
385 ))
386 })?;
387
388 let host = parsed_url
389 .host_str()
390 .ok_or_else(|| SdkError::Generic(format!("No host found in URL: {url}")))?;
391
392 let port = if parsed_url.port().is_some() {
395 parsed_url.port()
396 } else if url.contains(':') && !url.starts_with("http://") && !url.starts_with("https://") {
397 if let Some(host_port) = url.split('/').next() {
400 if let Some(port_str) = host_port.split(':').nth(1) {
401 port_str.parse::<u16>().ok()
402 } else {
403 None
404 }
405 } else {
406 None
407 }
408 } else {
409 parsed_url.port()
410 };
411
412 Ok((host.to_string(), port))
413 }
414
415 pub async fn collect_signoffs(
418 &self,
419 initial_token_response: TokenResponse,
420 resource: &str,
421 operation: &str,
422 ) -> Result<String, SdkError> {
423 let pending_signoffs = match &initial_token_response.pending_signoffs {
425 Some(signoffs) if !signoffs.is_empty() => signoffs,
426 _ => {
427 return initial_token_response
428 .token
429 .ok_or_else(|| SdkError::Generic("No token in response".to_string()))
430 }
431 };
432
433 let mut current_token = initial_token_response.token.ok_or_else(|| {
434 SdkError::Generic("No initial token to collect signoffs for".to_string())
435 })?;
436
437 for signoff_info in pending_signoffs {
439 let (base_url, port) =
441 Self::parse_authorization_service_url(&signoff_info.authorization_service)?;
442
443 let mut client_builder = HessraClientBuilder::new()
447 .base_url(base_url)
448 .protocol(self.config.protocol.clone())
449 .server_ca(self.config.server_ca.clone());
450
451 if let (Some(cert), Some(key)) = (&self.config.mtls_cert, &self.config.mtls_key) {
453 client_builder = client_builder.mtls_cert(cert.clone()).mtls_key(key.clone());
454 }
455
456 if let Some(port) = port {
457 client_builder = client_builder.port(port);
458 }
459
460 let signoff_client = client_builder
461 .build()
462 .map_err(|e| SdkError::Generic(format!("Failed to create signoff client: {e}")))?;
463
464 let sign_response = signoff_client
465 .sign_token(¤t_token, resource, operation)
466 .await
467 .map_err(|e| {
468 SdkError::Generic(format!(
469 "Signoff failed for {}: {e}",
470 signoff_info.component
471 ))
472 })?;
473
474 current_token = sign_response.signed_token.ok_or_else(|| {
475 SdkError::Generic(format!(
476 "No signed token returned from {}: {}",
477 signoff_info.component, sign_response.response_msg
478 ))
479 })?;
480 }
481
482 Ok(current_token)
483 }
484
485 pub async fn request_token_with_signoffs(
488 &self,
489 resource: &str,
490 operation: &str,
491 ) -> Result<String, SdkError> {
492 let initial_response = self.request_token(resource, operation, None).await?;
493 self.collect_signoffs(initial_response, resource, operation)
494 .await
495 }
496
497 pub async fn verify_token(
503 &self,
504 token: impl Into<String>,
505 subject: impl Into<String>,
506 resource: impl Into<String>,
507 operation: impl Into<String>,
508 ) -> Result<(), SdkError> {
509 if self.config.public_key.is_some() {
510 self.verify_token_local(
511 token.into(),
512 subject.into(),
513 resource.into(),
514 operation.into(),
515 )
516 } else {
517 self.verify_token_remote(
518 token.into(),
519 subject.into(),
520 resource.into(),
521 operation.into(),
522 )
523 .await
524 .map(|_| ())
525 .map_err(|e| SdkError::Generic(e.to_string()))
526 }
527 }
528
529 pub async fn verify_token_remote(
531 &self,
532 token: impl Into<String>,
533 subject: impl Into<String>,
534 resource: impl Into<String>,
535 operation: impl Into<String>,
536 ) -> Result<String, SdkError> {
537 self.client
538 .verify_token(
539 token.into(),
540 subject.into(),
541 resource.into(),
542 operation.into(),
543 )
544 .await
545 .map_err(|e| SdkError::Generic(e.to_string()))
546 }
547
548 pub fn verify_token_local(
550 &self,
551 token: impl Into<String>,
552 subject: impl AsRef<str>,
553 resource: impl AsRef<str>,
554 operation: impl AsRef<str>,
555 ) -> Result<(), SdkError> {
556 let public_key_str = match &self.config.public_key {
557 Some(key) => key,
558 None => return Err(SdkError::Generic("Public key not configured".to_string())),
559 };
560
561 let public_key = PublicKey::from_pem(public_key_str.as_str())
562 .map_err(|e| SdkError::Token(TokenError::Generic(e.to_string())))?;
563
564 let token_vec = decode_token(&token.into())?;
566
567 verify_biscuit_local(
568 token_vec,
569 public_key,
570 subject.as_ref().to_string(),
571 resource.as_ref().to_string(),
572 operation.as_ref().to_string(),
573 )
574 .map_err(SdkError::Token)
575 }
576
577 pub async fn verify_service_chain_token(
583 &self,
584 token: impl Into<String>,
585 subject: impl Into<String>,
586 resource: impl Into<String>,
587 operation: impl Into<String>,
588 service_chain: Option<&ServiceChain>,
589 component: Option<String>,
590 ) -> Result<(), SdkError> {
591 match (&self.config.public_key, service_chain) {
592 (Some(_), Some(chain)) => self.verify_service_chain_token_local(
593 token.into(),
594 subject.into(),
595 resource.into(),
596 operation.into(),
597 chain,
598 component,
599 ),
600 _ => self
601 .verify_service_chain_token_remote(
602 token.into(),
603 subject.into(),
604 resource.into(),
605 component,
606 )
607 .await
608 .map(|_| ())
609 .map_err(|e| SdkError::Generic(e.to_string())),
610 }
611 }
612
613 pub async fn verify_service_chain_token_remote(
615 &self,
616 token: impl Into<String>,
617 subject: impl Into<String>,
618 resource: impl Into<String>,
619 component: Option<String>,
620 ) -> Result<String, SdkError> {
621 self.client
622 .verify_service_chain_token(token.into(), subject.into(), resource.into(), component)
623 .await
624 .map_err(|e| SdkError::Generic(e.to_string()))
625 }
626
627 pub fn verify_service_chain_token_local(
629 &self,
630 token: String,
631 subject: impl AsRef<str>,
632 resource: impl AsRef<str>,
633 operation: impl AsRef<str>,
634 service_chain: &ServiceChain,
635 component: Option<String>,
636 ) -> Result<(), SdkError> {
637 let public_key_str = match &self.config.public_key {
638 Some(key) => key,
639 None => return Err(SdkError::Generic("Public key not configured".to_string())),
640 };
641
642 let public_key = PublicKey::from_pem(public_key_str.as_str())
643 .map_err(|e| SdkError::Token(TokenError::Generic(e.to_string())))?;
644
645 let token_vec = decode_token(&token)?;
647
648 verify_service_chain_biscuit_local(
649 token_vec,
650 public_key,
651 subject.as_ref().to_string(),
652 resource.as_ref().to_string(),
653 operation.as_ref().to_string(),
654 service_chain.to_internal(),
655 component,
656 )
657 .map_err(SdkError::Token)
658 }
659
660 pub fn attest_service_chain_token(
664 &self,
665 token: String,
666 service: impl Into<String>,
667 ) -> Result<String, SdkError> {
668 let keypair_str = match &self.config.personal_keypair {
669 Some(keypair) => keypair,
670 None => {
671 return Err(SdkError::Generic(
672 "Personal keypair not configured".to_string(),
673 ))
674 }
675 };
676
677 let public_key_str = match &self.config.public_key {
678 Some(key) => key,
679 None => return Err(SdkError::Generic("Public key not configured".to_string())),
680 };
681
682 let keypair = KeyPair::from_private_key_pem(keypair_str.as_str())
684 .map_err(|e| SdkError::Token(TokenError::Generic(e.to_string())))?;
685
686 let public_key = PublicKey::from_pem(public_key_str.as_str())
688 .map_err(|e| SdkError::Token(TokenError::Generic(e.to_string())))?;
689
690 let token_vec = decode_token(&token)?;
692
693 let service_str = service.into();
695
696 let token_vec = add_service_node_attestation(token_vec, public_key, &service_str, &keypair)
697 .map_err(SdkError::Token)?;
698
699 Ok(encode_token(&token_vec))
700 }
701
702 pub async fn get_public_key(&self) -> Result<String, SdkError> {
704 self.client
705 .get_public_key()
706 .await
707 .map_err(|e| SdkError::Generic(e.to_string()))
708 }
709
710 pub async fn request_identity_token(
719 &self,
720 identifier: Option<String>,
721 ) -> Result<IdentityTokenResponse, SdkError> {
722 self.client
723 .request_identity_token(identifier)
724 .await
725 .map_err(|e| SdkError::Generic(e.to_string()))
726 }
727
728 pub async fn refresh_identity_token(
738 &self,
739 current_token: impl Into<String>,
740 identifier: Option<String>,
741 ) -> Result<IdentityTokenResponse, SdkError> {
742 self.client
743 .refresh_identity_token(current_token.into(), identifier)
744 .await
745 .map_err(|e| SdkError::Generic(e.to_string()))
746 }
747
748 pub async fn mint_domain_restricted_identity_token(
775 &self,
776 subject: impl Into<String>,
777 duration: Option<u64>,
778 ) -> Result<MintIdentityTokenResponse, SdkError> {
779 self.client
780 .mint_domain_restricted_identity_token(subject.into(), duration)
781 .await
782 .map_err(|e| SdkError::Generic(e.to_string()))
783 }
784
785 pub async fn request_stub_token(
819 &self,
820 target_identity: impl Into<String>,
821 resource: impl Into<String>,
822 operation: impl Into<String>,
823 prefix_attenuator_key: impl Into<String>,
824 duration: Option<u64>,
825 ) -> Result<StubTokenResponse, SdkError> {
826 self.client
827 .request_stub_token(
828 target_identity.into(),
829 resource.into(),
830 operation.into(),
831 prefix_attenuator_key.into(),
832 duration,
833 )
834 .await
835 .map_err(|e| SdkError::Generic(e.to_string()))
836 }
837
838 pub async fn request_stub_token_with_identity(
852 &self,
853 target_identity: impl Into<String>,
854 resource: impl Into<String>,
855 operation: impl Into<String>,
856 prefix_attenuator_key: impl Into<String>,
857 identity_token: impl Into<String>,
858 duration: Option<u64>,
859 ) -> Result<StubTokenResponse, SdkError> {
860 let token = identity_token.into();
861 let attenuated_token = self.apply_jit_attenuation(token);
863
864 self.client
865 .request_stub_token_with_identity(
866 target_identity.into(),
867 resource.into(),
868 operation.into(),
869 prefix_attenuator_key.into(),
870 attenuated_token,
871 duration,
872 )
873 .await
874 .map_err(|e| SdkError::Generic(e.to_string()))
875 }
876
877 pub fn verify_identity_token_local(
886 &self,
887 token: impl Into<String>,
888 identity: impl Into<String>,
889 ) -> Result<(), SdkError> {
890 let public_key_str = match &self.config.public_key {
891 Some(key) => key,
892 None => return Err(SdkError::Generic("Public key not configured".to_string())),
893 };
894
895 let public_key = PublicKey::from_pem(public_key_str.as_str())
896 .map_err(|e| SdkError::Token(TokenError::Generic(e.to_string())))?;
897
898 verify_identity_token(token.into(), public_key, identity.into())
899 .map_err(|e| SdkError::Token(TokenError::Generic(e.to_string())))
900 }
901
902 pub fn attenuate_identity_token(
914 &self,
915 token: impl Into<String>,
916 delegated_identity: impl Into<String>,
917 duration: i64,
918 ) -> Result<String, SdkError> {
919 let public_key_str = match &self.config.public_key {
920 Some(key) => key,
921 None => return Err(SdkError::Generic("Public key not configured".to_string())),
922 };
923
924 let public_key = PublicKey::from_pem(public_key_str.as_str())
925 .map_err(|e| SdkError::Token(TokenError::Generic(e.to_string())))?;
926
927 let time_config = hessra_token_core::TokenTimeConfig {
928 start_time: None,
929 duration,
930 };
931
932 add_identity_attenuation_to_token(
933 token.into(),
934 delegated_identity.into(),
935 public_key,
936 time_config,
937 )
938 .map_err(|e| SdkError::Token(TokenError::Generic(e.to_string())))
939 }
940
941 pub fn create_identity_token_local(
951 &self,
952 subject: impl Into<String>,
953 duration: i64,
954 ) -> Result<String, SdkError> {
955 let keypair_str = match &self.config.personal_keypair {
956 Some(keypair) => keypair,
957 None => {
958 return Err(SdkError::Generic(
959 "Personal keypair not configured".to_string(),
960 ))
961 }
962 };
963
964 let keypair = KeyPair::from_private_key_pem(keypair_str.as_str())
965 .map_err(|e| SdkError::Token(TokenError::Generic(e.to_string())))?;
966
967 let time_config = hessra_token_core::TokenTimeConfig {
968 start_time: None,
969 duration,
970 };
971
972 create_identity_token(subject.into(), keypair, time_config)
973 .map_err(|e| SdkError::Token(TokenError::Generic(e.to_string())))
974 }
975
976 pub fn client(&self) -> &HessraClient {
978 &self.client
979 }
980
981 pub fn config(&self) -> &HessraConfig {
983 &self.config
984 }
985}
986
987#[derive(Default)]
989pub struct HessraBuilder {
990 config_builder: hessra_config::HessraConfigBuilder,
991}
992
993impl HessraBuilder {
994 pub fn new() -> Self {
996 Self {
997 config_builder: HessraConfig::builder(),
998 }
999 }
1000
1001 pub fn base_url(mut self, base_url: impl Into<String>) -> Self {
1012 let base_url_str = base_url.into();
1013 let (host, embedded_port) = parse_server_address(&base_url_str);
1014
1015 self.config_builder = self.config_builder.base_url(host);
1016
1017 if let Some(port) = embedded_port {
1019 self.config_builder = self.config_builder.port(port);
1020 }
1021
1022 self
1023 }
1024
1025 pub fn mtls_key(mut self, mtls_key: impl Into<String>) -> Self {
1027 self.config_builder = self.config_builder.mtls_key(mtls_key);
1028 self
1029 }
1030
1031 pub fn mtls_cert(mut self, mtls_cert: impl Into<String>) -> Self {
1033 self.config_builder = self.config_builder.mtls_cert(mtls_cert);
1034 self
1035 }
1036
1037 pub fn server_ca(mut self, server_ca: impl Into<String>) -> Self {
1039 self.config_builder = self.config_builder.server_ca(server_ca);
1040 self
1041 }
1042
1043 pub fn port(mut self, port: u16) -> Self {
1045 self.config_builder = self.config_builder.port(port);
1046 self
1047 }
1048
1049 pub fn protocol(mut self, protocol: Protocol) -> Self {
1051 self.config_builder = self.config_builder.protocol(protocol);
1052 self
1053 }
1054
1055 pub fn public_key(mut self, public_key: impl Into<String>) -> Self {
1057 self.config_builder = self.config_builder.public_key(public_key);
1058 self
1059 }
1060
1061 pub fn personal_keypair(mut self, keypair: impl Into<String>) -> Self {
1063 self.config_builder = self.config_builder.personal_keypair(keypair);
1064 self
1065 }
1066
1067 pub fn build(self) -> Result<Hessra, SdkError> {
1069 let config = self.config_builder.build()?;
1070 Hessra::new(config)
1071 }
1072}
1073
1074pub async fn fetch_public_key(
1078 base_url: impl Into<String>,
1079 port: Option<u16>,
1080 server_ca: impl Into<String>,
1081) -> Result<String, SdkError> {
1082 HessraClient::fetch_public_key(base_url, port, server_ca)
1083 .await
1084 .map_err(|e| SdkError::Generic(e.to_string()))
1085}
1086
1087#[cfg(feature = "http3")]
1091pub async fn fetch_public_key_http3(
1092 base_url: impl Into<String>,
1093 port: Option<u16>,
1094 server_ca: impl Into<String>,
1095) -> Result<String, SdkError> {
1096 HessraClient::fetch_public_key_http3(base_url, port, server_ca)
1097 .await
1098 .map_err(|e| SdkError::Generic(e.to_string()))
1099}
1100
1101pub async fn fetch_ca_cert(
1109 base_url: impl Into<String>,
1110 port: Option<u16>,
1111) -> Result<String, SdkError> {
1112 HessraClient::fetch_ca_cert(base_url, port)
1113 .await
1114 .map_err(|e| SdkError::Generic(e.to_string()))
1115}
1116
1117#[cfg(test)]
1118mod tests {
1119 use super::*;
1120
1121 #[test]
1122 fn test_service_chain_creation() {
1123 let json = r#"[
1125 {
1126 "component": "service1",
1127 "public_key": "ed25519/abcdef1234567890"
1128 },
1129 {
1130 "component": "service2",
1131 "public_key": "ed25519/0987654321fedcba"
1132 }
1133 ]"#;
1134
1135 let service_chain = ServiceChain::from_json(json).unwrap();
1136 assert_eq!(service_chain.nodes().len(), 2);
1137 assert_eq!(service_chain.nodes()[0].component, "service1");
1138 assert_eq!(
1139 service_chain.nodes()[0].public_key,
1140 "ed25519/abcdef1234567890"
1141 );
1142 assert_eq!(service_chain.nodes()[1].component, "service2");
1143 assert_eq!(
1144 service_chain.nodes()[1].public_key,
1145 "ed25519/0987654321fedcba"
1146 );
1147
1148 let mut chain = ServiceChain::new();
1150 let node = ServiceNode {
1151 component: "service3".to_string(),
1152 public_key: "ed25519/1122334455667788".to_string(),
1153 };
1154 chain.add_node(node);
1155 assert_eq!(chain.nodes().len(), 1);
1156 assert_eq!(chain.nodes()[0].component, "service3");
1157 }
1158
1159 #[test]
1160 fn test_service_chain_builder() {
1161 let builder = ServiceChainBuilder::new();
1162 let node1 = ServiceNode {
1163 component: "auth".to_string(),
1164 public_key: "ed25519/auth123".to_string(),
1165 };
1166 let node2 = ServiceNode {
1167 component: "payment".to_string(),
1168 public_key: "ed25519/payment456".to_string(),
1169 };
1170
1171 let chain = builder.add_node(node1).add_node(node2).build();
1172
1173 assert_eq!(chain.nodes().len(), 2);
1174 assert_eq!(chain.nodes()[0].component, "auth");
1175 assert_eq!(chain.nodes()[1].component, "payment");
1176 }
1177
1178 #[test]
1179 fn test_parse_authorization_service_url() {
1180 let (base_url, port) =
1182 Hessra::parse_authorization_service_url("https://127.0.0.1:4433/sign_token").unwrap();
1183 assert_eq!(base_url, "127.0.0.1");
1184 assert_eq!(port, Some(4433));
1185
1186 let (base_url, port) =
1188 Hessra::parse_authorization_service_url("http://example.com:8080/api/sign").unwrap();
1189 assert_eq!(base_url, "example.com");
1190 assert_eq!(port, Some(8080));
1191
1192 let (base_url, port) =
1194 Hessra::parse_authorization_service_url("test.hessra.net:443/sign_token").unwrap();
1195 assert_eq!(base_url, "test.hessra.net");
1196 assert_eq!(port, Some(443));
1197
1198 let (base_url, port) =
1200 Hessra::parse_authorization_service_url("example.com/api/endpoint").unwrap();
1201 assert_eq!(base_url, "example.com");
1202 assert_eq!(port, None);
1203
1204 let (base_url, port) =
1206 Hessra::parse_authorization_service_url("https://localhost:8443").unwrap();
1207 assert_eq!(base_url, "localhost");
1208 assert_eq!(port, Some(8443));
1209
1210 let (base_url, port) = Hessra::parse_authorization_service_url("api.example.org").unwrap();
1212 assert_eq!(base_url, "api.example.org");
1213 assert_eq!(port, None);
1214 }
1215}