1use azure_core::http::Url;
4
5#[derive(Debug, thiserror::Error, Clone, PartialEq, Eq)]
7pub enum FormatError {
8 #[error("Connection string cannot be empty")]
10 ConnectionStringIsEmpty,
11
12 #[error("Connection string is malformed")]
14 InvalidConnectionString,
15}
16
17impl From<FormatError> for azure_core::Error {
18 fn from(value: FormatError) -> Self {
19 azure_core::Error::new(azure_core::error::ErrorKind::Other, value)
20 }
21}
22
23#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
25pub enum ToConnectionStringError {
26 #[error("Missing connection information")]
28 MissingConnectionInformation,
29
30 #[error("Invalid endpoint address")]
32 InvalidEndpointAddress,
33
34 #[error("Only one shared access authorization can be used")]
36 OnlyOneSharedAccessAuthorizationMayBeUsed,
37}
38
39#[derive(Debug, PartialEq, Eq, Hash)]
41pub struct ServiceBusConnectionStringProperties<'a> {
42 pub(crate) endpoint: Option<url::Url>,
43 pub(crate) entity_path: Option<&'a str>,
44 pub(crate) shared_access_key_name: Option<&'a str>,
45 pub(crate) shared_access_key: Option<&'a str>,
46 pub(crate) shared_access_signature: Option<&'a str>,
47}
48
49impl<'a> ServiceBusConnectionStringProperties<'a> {
50 const TOKEN_VALUE_SEPARATOR: char = '=';
52
53 const TOKEN_VALUE_PAIR_DELIMITER: char = ';';
55
56 const SERVICE_BUS_ENDPOINT_SCHEME_NAME: &'static str = "sb";
58
59 const ENDPOINT_TOKEN: &'static str = "Endpoint";
61
62 const ENTITY_PATH_TOKEN: &'static str = "EntityPath";
64
65 const SHARED_ACCESS_KEY_NAME_TOKEN: &'static str = "SharedAccessKeyName";
67
68 const SHARED_ACCESS_KEY_VALUE_TOKEN: &'static str = "SharedAccessKey";
70
71 const SHARED_ACCESS_SIGNATURE_TOKEN: &'static str = "SharedAccessSignature";
73
74 pub fn fully_qualified_namespace(&self) -> Option<&str> {
77 self.endpoint.as_ref().and_then(|url| url.host_str())
78 }
79
80 pub fn endpoint(&self) -> Option<&Url> {
82 self.endpoint.as_ref()
83 }
84
85 pub fn entity_path(&self) -> Option<&str> {
88 self.entity_path
89 }
90
91 pub fn shared_access_key_name(&self) -> Option<&str> {
94 self.shared_access_key_name
95 }
96
97 pub fn shared_access_key(&self) -> Option<&str> {
100 self.shared_access_key
101 }
102
103 pub fn shared_access_signature(&self) -> Option<&str> {
106 self.shared_access_signature
107 }
108
109 pub fn to_connection_string(&self) -> Result<String, ToConnectionStringError> {
112 let mut s = String::new();
113
114 if let Some(endpoint) = self.endpoint() {
115 if endpoint.scheme() != Self::SERVICE_BUS_ENDPOINT_SCHEME_NAME {
116 return Err(ToConnectionStringError::InvalidEndpointAddress);
118 }
119
120 s.push_str(Self::ENDPOINT_TOKEN);
121 s.push(Self::TOKEN_VALUE_SEPARATOR);
122 s.push_str(endpoint.as_str());
123 s.push(Self::TOKEN_VALUE_PAIR_DELIMITER);
124 } else {
125 return Err(ToConnectionStringError::MissingConnectionInformation);
126 }
127
128 if let Some(entity_path) = self.entity_path.and_then(|s| match !s.is_empty() {
129 true => Some(s),
130 false => None,
131 }) {
132 s.push_str(Self::ENTITY_PATH_TOKEN);
133 s.push(Self::TOKEN_VALUE_SEPARATOR);
134 s.push_str(entity_path);
135 s.push(Self::TOKEN_VALUE_PAIR_DELIMITER);
136 }
137
138 match (
141 self.shared_access_signature,
142 self.shared_access_key_name,
143 self.shared_access_key,
144 ) {
145 (Some(signature), None, None) => {
146 if !signature.is_empty() {
147 s.push_str(Self::SHARED_ACCESS_SIGNATURE_TOKEN);
148 s.push(Self::TOKEN_VALUE_SEPARATOR);
149 s.push_str(signature);
150 s.push(Self::TOKEN_VALUE_PAIR_DELIMITER);
151 }
152 }
153 (None, Some(key_name), Some(key)) => {
154 if (!key_name.is_empty()) && (!key.is_empty()) {
155 s.push_str(Self::SHARED_ACCESS_KEY_NAME_TOKEN);
156 s.push(Self::TOKEN_VALUE_SEPARATOR);
157 s.push_str(key_name);
158 s.push(Self::TOKEN_VALUE_PAIR_DELIMITER);
159
160 s.push_str(Self::SHARED_ACCESS_KEY_VALUE_TOKEN);
161 s.push(Self::TOKEN_VALUE_SEPARATOR);
162 s.push_str(key);
163 s.push(Self::TOKEN_VALUE_PAIR_DELIMITER);
164 }
165 }
166 _ => {
167 return Err(ToConnectionStringError::OnlyOneSharedAccessAuthorizationMayBeUsed);
168 }
169 }
170
171 Ok(s)
172 }
173
174 pub fn parse(connection_string: &'a str) -> Result<Self, FormatError> {
176 if connection_string.is_empty() {
177 return Err(FormatError::ConnectionStringIsEmpty);
178 }
179
180 let mut endpoint: Option<Url> = None;
181 let mut entity_path: Option<&'a str> = None;
182 let mut shared_access_key_name: Option<&'a str> = None;
183 let mut shared_access_key: Option<&'a str> = None;
184 let mut shared_access_signature: Option<&'a str> = None;
185
186 let token_value_pairs = connection_string.split(Self::TOKEN_VALUE_PAIR_DELIMITER);
187
188 for token_value_pair in token_value_pairs {
189 if token_value_pair.is_empty() {
190 continue;
191 }
192
193 let (token, value) = token_value_pair
194 .split_once(Self::TOKEN_VALUE_SEPARATOR)
195 .ok_or(FormatError::InvalidConnectionString)?;
196
197 let token = token.trim();
198 if token.is_empty() {
199 continue;
200 }
201
202 let value = value.trim();
203 if value.is_empty() {
204 continue;
205 }
206
207 match token {
210 Self::ENDPOINT_TOKEN => {
211 let mut url =
213 Url::parse(value).map_err(|_| FormatError::InvalidConnectionString)?;
214 url.set_scheme(Self::SERVICE_BUS_ENDPOINT_SCHEME_NAME)
215 .map_err(|_| FormatError::InvalidConnectionString)?;
216 endpoint = Some(url);
217 }
218 Self::ENTITY_PATH_TOKEN => entity_path = Some(value),
219 Self::SHARED_ACCESS_KEY_NAME_TOKEN => shared_access_key_name = Some(value),
220 Self::SHARED_ACCESS_KEY_VALUE_TOKEN => shared_access_key = Some(value),
221 Self::SHARED_ACCESS_SIGNATURE_TOKEN => shared_access_signature = Some(value),
222 _ => {}
223 }
224 }
225
226 Ok(Self {
227 endpoint,
228 entity_path,
229 shared_access_key_name,
230 shared_access_key,
231 shared_access_signature,
232 })
233 }
234}
235
236#[cfg(test)]
237mod tests {
238 use crate::primitives::service_bus_connection_string_properties::FormatError;
239
240 use super::ServiceBusConnectionStringProperties;
241
242 const ENDPOINT: &str = "test.endpoint.com";
243 const EVENT_HUB: &str = "some-path";
244 const SAS_KEY_NAME: &str = "sasName";
245 const SAS_KEY: &str = "sasKey";
246 const SAS: &str = "fullsas";
247
248 struct Expected {
249 endpoint: Option<&'static str>,
250 event_hub: Option<&'static str>,
251 sas_key_name: Option<&'static str>,
252 sas_key: Option<&'static str>,
253 sas: Option<&'static str>,
254 }
255
256 macro_rules! assert_parsed_and_expected {
257 ($connection_string:ident, $expected:ident) => {
258 let parsed = ServiceBusConnectionStringProperties::parse(&$connection_string).unwrap();
259
260 assert_eq!(
261 parsed.endpoint().and_then(|url| url.host_str()),
262 $expected.endpoint
263 );
264 assert_eq!(parsed.shared_access_key_name(), $expected.sas_key_name);
265 assert_eq!(parsed.shared_access_key(), $expected.sas_key);
266 assert_eq!(parsed.shared_access_signature(), $expected.sas);
267 assert_eq!(parsed.entity_path(), $expected.event_hub);
268 };
269 }
270
271 fn random_ordering_connection_string_cases() -> Vec<(String, Expected)> {
272 vec![
273 (
274 format!(
275 "Endpoint=sb://{};SharedAccessKeyName={};SharedAccessKey={};EntityPath={}",
276 ENDPOINT, SAS_KEY_NAME, SAS_KEY, EVENT_HUB
277 ),
278 Expected {
279 endpoint: Some(ENDPOINT),
280 event_hub: Some(EVENT_HUB),
281 sas_key_name: Some(SAS_KEY_NAME),
282 sas_key: Some(SAS_KEY),
283 sas: None,
284 },
285 ),
286 (
287 format!(
288 "Endpoint=sb://{};SharedAccessKey={};EntityPath={};SharedAccessKeyName={}",
289 ENDPOINT, SAS_KEY, EVENT_HUB, SAS_KEY_NAME,
290 ),
291 Expected {
292 endpoint: Some(ENDPOINT),
293 event_hub: Some(EVENT_HUB),
294 sas_key_name: Some(SAS_KEY_NAME),
295 sas_key: Some(SAS_KEY),
296 sas: None,
297 },
298 ),
299 (
300 format!(
301 "Endpoint=sb://{};EntityPath={};SharedAccessKeyName={};SharedAccessKey={}",
302 ENDPOINT, EVENT_HUB, SAS_KEY_NAME, SAS_KEY
303 ),
304 Expected {
305 endpoint: Some(ENDPOINT),
306 event_hub: Some(EVENT_HUB),
307 sas_key_name: Some(SAS_KEY_NAME),
308 sas_key: Some(SAS_KEY),
309 sas: None,
310 },
311 ),
312 (
313 format!(
314 "SharedAccessKeyName={};SharedAccessKey={};Endpoint=sb://{};EntityPath={}",
315 SAS_KEY_NAME, SAS_KEY, ENDPOINT, EVENT_HUB
316 ),
317 Expected {
318 endpoint: Some(ENDPOINT),
319 event_hub: Some(EVENT_HUB),
320 sas_key_name: Some(SAS_KEY_NAME),
321 sas_key: Some(SAS_KEY),
322 sas: None,
323 },
324 ),
325 (
326 format!(
327 "EntityPath={};SharedAccessKey={};SharedAccessKeyName={};Endpoint=sb://{}",
328 EVENT_HUB, SAS_KEY, SAS_KEY_NAME, ENDPOINT
329 ),
330 Expected {
331 endpoint: Some(ENDPOINT),
332 event_hub: Some(EVENT_HUB),
333 sas_key_name: Some(SAS_KEY_NAME),
334 sas_key: Some(SAS_KEY),
335 sas: None,
336 },
337 ),
338 (
339 format!(
340 "EntityPath={};SharedAccessSignature={};Endpoint=sb://{}",
341 EVENT_HUB, SAS, ENDPOINT,
342 ),
343 Expected {
344 endpoint: Some(ENDPOINT),
345 event_hub: Some(EVENT_HUB),
346 sas_key_name: None,
347 sas_key: None,
348 sas: Some(SAS),
349 },
350 ),
351 (
352 format!(
353 "SharedAccessKeyName={};SharedAccessKey={};Endpoint=sb://{};EntityPath={};SharedAccessSignature={}",
354 SAS_KEY_NAME, SAS_KEY, ENDPOINT, EVENT_HUB, SAS
355 ),
356 Expected {
357 endpoint: Some(ENDPOINT),
358 event_hub: Some(EVENT_HUB),
359 sas_key_name: Some(SAS_KEY_NAME),
360 sas_key: Some(SAS_KEY),
361 sas: Some(SAS),
362 },
363 ),
364 ]
365 }
366
367 fn partial_connection_string_cases() -> Vec<(String, Expected)> {
368 vec![
369 (
370 format!("Endpoint=sb://{}", ENDPOINT),
371 Expected {
372 endpoint: Some(ENDPOINT),
373 event_hub: None,
374 sas_key_name: None,
375 sas_key: None,
376 sas: None,
377 },
378 ),
379 (
380 format!("SharedAccessKey={}", SAS_KEY),
381 Expected {
382 endpoint: None,
383 event_hub: None,
384 sas_key_name: None,
385 sas_key: Some(SAS_KEY),
386 sas: None,
387 },
388 ),
389 (
390 format!(
391 "EntityPath={};SharedAccessKeyName={}",
392 EVENT_HUB, SAS_KEY_NAME
393 ),
394 Expected {
395 endpoint: None,
396 event_hub: Some(EVENT_HUB),
397 sas_key_name: Some(SAS_KEY_NAME),
398 sas_key: None,
399 sas: None,
400 },
401 ),
402 (
403 format!(
404 "SharedAccessKeyName={};SharedAccessKey={}",
405 SAS_KEY_NAME, SAS_KEY
406 ),
407 Expected {
408 endpoint: None,
409 event_hub: None,
410 sas_key_name: Some(SAS_KEY_NAME),
411 sas_key: Some(SAS_KEY),
412 sas: None,
413 },
414 ),
415 (
416 format!(
417 "EntityPath={};SharedAccessKey={};SharedAccessKeyName={}",
418 EVENT_HUB, SAS_KEY, SAS_KEY_NAME
419 ),
420 Expected {
421 endpoint: None,
422 event_hub: Some(EVENT_HUB),
423 sas_key_name: Some(SAS_KEY_NAME),
424 sas_key: Some(SAS_KEY),
425 sas: None,
426 },
427 ),
428 (
429 format!(
430 "SharedAccessKeyName={};SharedAccessSignature={}",
431 SAS_KEY_NAME, SAS
432 ),
433 Expected {
434 endpoint: None,
435 event_hub: None,
436 sas_key_name: Some(SAS_KEY_NAME),
437 sas_key: None,
438 sas: Some(SAS),
439 },
440 ),
441 (
442 format!("EntityPath={};SharedAccessSignature={}", EVENT_HUB, SAS),
443 Expected {
444 endpoint: None,
445 event_hub: Some(EVENT_HUB),
446 sas_key_name: None,
447 sas_key: None,
448 sas: Some(SAS),
449 },
450 ),
451 (
452 format!(
453 "EntityPath={};SharedAccessKey={};SharedAccessKeyName={};SharedAccessSignature={}",
454 EVENT_HUB, SAS_KEY, SAS_KEY_NAME, SAS
455 ),
456 Expected {
457 endpoint: None,
458 event_hub: Some(EVENT_HUB),
459 sas_key_name: Some(SAS_KEY_NAME),
460 sas_key: Some(SAS_KEY),
461 sas: Some(SAS),
462 },
463 ),
464 ]
465 }
466
467 fn to_connection_string_validates_properties_cases(
468 ) -> Vec<ServiceBusConnectionStringProperties<'static>> {
469 let mut cases = Vec::new();
470 let case = ServiceBusConnectionStringProperties {
472 endpoint: None,
473 entity_path: Some("fake"),
474 shared_access_signature: Some("fake"),
475 shared_access_key_name: None,
476 shared_access_key: None,
477 };
478 cases.push(case);
479
480 let case = ServiceBusConnectionStringProperties {
482 endpoint: Some(url::Url::parse("sb://someplace.hosname.ext").unwrap()),
483 entity_path: Some("fake"),
484 shared_access_signature: None,
485 shared_access_key_name: None,
486 shared_access_key: None,
487 };
488 cases.push(case);
489
490 let case = ServiceBusConnectionStringProperties {
492 endpoint: Some(url::Url::parse("sb://someplace.hosname.ext").unwrap()),
493 entity_path: Some("fake"),
494 shared_access_signature: Some("fake"),
495 shared_access_key: Some("fake"),
496 shared_access_key_name: None,
497 };
498 cases.push(case);
499
500 let case = ServiceBusConnectionStringProperties {
502 endpoint: Some(url::Url::parse("sb://someplace.hosname.ext").unwrap()),
503 entity_path: Some("fake"),
504 shared_access_signature: Some("fake"),
505 shared_access_key_name: Some("fake"),
506 shared_access_key: None,
507 };
508 cases.push(case);
509
510 let case = ServiceBusConnectionStringProperties {
512 endpoint: Some(url::Url::parse("sb://someplace.hosname.ext").unwrap()),
513 entity_path: Some("fake"),
514 shared_access_signature: None,
515 shared_access_key_name: Some("fake"),
516 shared_access_key: None,
517 };
518 cases.push(case);
519
520 let case = ServiceBusConnectionStringProperties {
522 endpoint: Some(url::Url::parse("sb://someplace.hosname.ext").unwrap()),
523 entity_path: Some("fake"),
524 shared_access_signature: None,
525 shared_access_key_name: None,
526 shared_access_key: Some("fake"),
527 };
528 cases.push(case);
529
530 cases
531 }
532
533 #[test]
534 fn parse_correctly_parses_a_namespace_connection_string() {
535 let endpoint = "test.endpoint.com";
536 let sas_key = "sasKey=";
537 let sas_key_name = "sasName";
538 let shared_access_signature = "fakeSAS";
539 let connection_string = format!("Endpoint=sb://{endpoint};SharedAccessKeyName={sas_key_name};SharedAccessKey={sas_key};SharedAccessSignature={shared_access_signature}");
540 let parsed = ServiceBusConnectionStringProperties::parse(&connection_string).unwrap();
541
542 assert_eq!(
543 parsed.endpoint().and_then(|url| url.host_str()),
544 Some(endpoint)
545 );
546 assert_eq!(parsed.shared_access_key_name(), Some(sas_key_name));
547 assert_eq!(parsed.shared_access_key(), Some(sas_key));
548 assert_eq!(
549 parsed.shared_access_signature(),
550 Some(shared_access_signature)
551 );
552 assert_eq!(parsed.entity_path(), None);
553 }
554
555 #[test]
556 fn parse_correctly_parses_an_entity_connection_string() {
557 let endpoint = "test.endpoint.com";
558 let event_hub = "some-path";
559 let sas_key = "sasKey";
560 let sas_key_name = "sasName";
561 let shared_access_signature = "fakeSAS";
562 let connection_string = format!("Endpoint=sb://{endpoint};SharedAccessKeyName={sas_key_name};SharedAccessKey={sas_key};EntityPath={event_hub};SharedAccessSignature={shared_access_signature}");
563 let parsed = ServiceBusConnectionStringProperties::parse(&connection_string).unwrap();
564
565 assert_eq!(
566 parsed.endpoint().and_then(|url| url.host_str()),
567 Some(endpoint)
568 );
569 assert_eq!(parsed.shared_access_key_name(), Some(sas_key_name));
570 assert_eq!(parsed.shared_access_key(), Some(sas_key));
571 assert_eq!(
572 parsed.shared_access_signature(),
573 Some(shared_access_signature)
574 );
575 assert_eq!(parsed.entity_path(), Some(event_hub));
576 }
577
578 #[test]
579 fn parse_correctly_parses_partial_connection_strings() {
580 let cases = partial_connection_string_cases();
581
582 for (connection_string, expected) in cases {
583 assert_parsed_and_expected!(connection_string, expected);
584 }
585 }
586
587 #[test]
588 fn parse_tolerates_leading_delimiters() {
589 let endpoint = "test.endpoint.com";
590 let event_hub = "some-path";
591 let sas_key = "sasKey";
592 let sas_key_name = "sasName";
593 let connection_string = format!(";Endpoint=sb://{endpoint};SharedAccessKeyName={sas_key_name};SharedAccessKey={sas_key};EntityPath={event_hub}");
594 let parsed = ServiceBusConnectionStringProperties::parse(&connection_string).unwrap();
595
596 assert_eq!(
597 parsed.endpoint().and_then(|url| url.host_str()),
598 Some(endpoint)
599 );
600 assert_eq!(parsed.shared_access_key_name(), Some(sas_key_name));
601 assert_eq!(parsed.shared_access_key(), Some(sas_key));
602 assert_eq!(parsed.entity_path(), Some(event_hub));
603 }
604
605 #[test]
606 fn parse_tolerates_spaces_between_pairs() {
607 let endpoint = "test.endpoint.com";
608 let event_hub = "some-path";
609 let sas_key = "sasKey";
610 let sas_key_name = "sasName";
611 let connection_string = format!("Endpoint=sb://{endpoint}; SharedAccessKeyName={sas_key_name}; SharedAccessKey={sas_key}; EntityPath={event_hub}");
612 let parsed = ServiceBusConnectionStringProperties::parse(&connection_string).unwrap();
613
614 assert_eq!(
615 parsed.endpoint().and_then(|url| url.host_str()),
616 Some(endpoint)
617 );
618 assert_eq!(parsed.shared_access_key_name(), Some(sas_key_name));
619 assert_eq!(parsed.shared_access_key(), Some(sas_key));
620 assert_eq!(parsed.entity_path(), Some(event_hub));
621 }
622
623 #[test]
624 fn parse_tolerates_spaces_between_values() {
625 let endpoint = "test.endpoint.com";
626 let event_hub = "some-path";
627 let sas_key = "sasKey";
628 let sas_key_name = "sasName";
629 let connection_string = format!("Endpoint = sb://{endpoint};SharedAccessKeyName ={sas_key_name};SharedAccessKey= {sas_key}; EntityPath = {event_hub}");
630 let parsed = ServiceBusConnectionStringProperties::parse(&connection_string).unwrap();
631
632 assert_eq!(
633 parsed.endpoint().and_then(|url| url.host_str()),
634 Some(endpoint)
635 );
636 assert_eq!(parsed.shared_access_key_name(), Some(sas_key_name));
637 assert_eq!(parsed.shared_access_key(), Some(sas_key));
638 assert_eq!(parsed.entity_path(), Some(event_hub));
639 }
640
641 #[test]
642 fn parse_does_not_force_token_ordering() {
643 let cases = random_ordering_connection_string_cases();
644
645 for (connection_string, expected) in cases {
646 assert_parsed_and_expected!(connection_string, expected);
647 }
648 }
649
650 #[test]
651 fn parse_ignores_unknown_tokens() {
652 let endpoint = "test.endpoint.com";
653 let event_hub = "some-path";
654 let sas_key = "sasKey";
655 let sas_key_name = "sasName";
656 let connection_string = format!("Endpoint=sb://{endpoint};SharedAccessKeyName={sas_key_name};Unknown=INVALID;SharedAccessKey={sas_key};EntityPath={event_hub};Trailing=WHOAREYOU");
657 let parsed = ServiceBusConnectionStringProperties::parse(&connection_string).unwrap();
658
659 assert_eq!(
660 parsed.endpoint().and_then(|url| url.host_str()),
661 Some(endpoint)
662 );
663 assert_eq!(parsed.shared_access_key_name(), Some(sas_key_name));
664 assert_eq!(parsed.shared_access_key(), Some(sas_key));
665 assert_eq!(parsed.entity_path(), Some(event_hub));
666 }
667
668 #[test]
669 fn parse_does_accept_host_names_and_urls_for_the_endpoint() {
670 let endpoint_values = &[
671 "sb://test.endpoint.com",
673 "sb://test.endpoint.com:80",
674 "amqp://test.endpoint.com",
675 ];
678
679 for endpoint_value in endpoint_values {
680 let connection_string = format!("Endpoint={};EntityPath=dummy", endpoint_value);
681 let parsed = ServiceBusConnectionStringProperties::parse(&connection_string).unwrap();
682
683 assert_eq!(
684 parsed.endpoint().and_then(|url| url.host_str()),
685 Some("test.endpoint.com")
686 );
687 }
688 }
689
690 #[test]
691 fn parse_does_not_allow_an_invalid_endpoint_format() {
692 let endpoint = "test.endpoint.com";
693 let connection_string = format!("Endpoint={}", endpoint);
694 let result = ServiceBusConnectionStringProperties::parse(&connection_string);
695 assert!(result.is_err());
696 }
697
698 #[test]
699 fn parse_considers_missing_values_as_malformed() {
700 let test_cases = &[
701 "Endpoint;SharedAccessKeyName=[value];SharedAccessKey=[value];EntityPath=[value]",
702 "Endpoint=value.com;SharedAccessKeyName=;SharedAccessKey=[value];EntityPath=[value]",
703 "Endpoint=value.com;SharedAccessKeyName=[value];SharedAccessKey;EntityPath=[value]",
704 "Endpoint=value.com;SharedAccessKeyName=[value];SharedAccessKey=[value];EntityPath",
705 "Endpoint;SharedAccessKeyName=;SharedAccessKey;EntityPath=",
706 "Endpoint=;SharedAccessKeyName;SharedAccessKey;EntityPath=",
707 ];
708
709 for test_case in test_cases {
710 let result = ServiceBusConnectionStringProperties::parse(test_case);
711 assert_eq!(result, Err(FormatError::InvalidConnectionString));
712 }
713 }
714
715 #[test]
716 fn parse_sas_only_connection_string() {
717 let sas_value = "SharedAccessSignature sr=sb%3A%2F%2Fmysb.servicebus.windows.net%2Fns%2Fsubscriptions%2Fabcd1234&sig=VRJrWUFA6CzMV2mebBb4zfUe6KjWOhJiHi1l5qbKLwg%3D&se=1751967277&skn=key-name";
718 let sas_connection_string =
719 format!("Endpoint=sb://mysb.servicebus.windows.net/;SharedAccessSignature={sas_value}");
720
721 let parsed = ServiceBusConnectionStringProperties::parse(&sas_connection_string).unwrap();
722
723 assert_eq!(parsed.shared_access_signature(), Some(sas_value));
724 }
725
726 #[test]
727 fn to_string_validates_properties() {
728 let cases = to_connection_string_validates_properties_cases();
729
730 for case in cases {
731 let result = case.to_connection_string();
732 assert!(result.is_err());
733 }
734 }
735
736 #[test]
737 fn to_connection_string_produces_the_connection_string_for_shared_access_signatures() {
738 let properties = ServiceBusConnectionStringProperties {
739 endpoint: Some("sb://place.endpoint.ext".parse().unwrap()),
740 entity_path: Some("HubName"),
741 shared_access_signature: Some("FaKe#$1324@@"),
742 shared_access_key_name: None,
743 shared_access_key: None,
744 };
745
746 let connection_string = properties.to_connection_string();
747 assert!(connection_string.is_ok());
748 let connection_string = connection_string.unwrap();
749 assert!(!connection_string.is_empty());
750
751 let parsed = ServiceBusConnectionStringProperties::parse(&connection_string);
752 assert!(parsed.is_ok());
753 assert_eq!(properties, parsed.unwrap());
754 }
755
756 #[test]
757 fn to_connection_string_produces_the_connection_string_for_shared_keys() {
758 let properties = ServiceBusConnectionStringProperties {
759 endpoint: Some("sb://place.endpoint.ext".parse().unwrap()),
760 entity_path: Some("HubName"),
761 shared_access_signature: None,
762 shared_access_key_name: Some("RootSharedAccessManagementKey"),
763 shared_access_key: Some("FaKe#$1324@@"),
764 };
765
766 let connection_string = properties.to_connection_string();
767 assert!(connection_string.is_ok());
768 let connection_string = connection_string.unwrap();
769 assert!(!connection_string.is_empty());
770
771 let parsed = ServiceBusConnectionStringProperties::parse(&connection_string);
772 assert!(parsed.is_ok());
773 assert_eq!(properties, parsed.unwrap());
774 }
775
776 #[test]
777 fn to_connection_string_returns_err_with_non_servicebus_endpoint_scheme() {
778 let schemes = vec![
779 "amqps://", "amqp://",
780 "http://", "https://", "fake://",
782 ];
783
784 for scheme in schemes {
785 let endpoint = format!("{}myhub.servicebus.windows.net", scheme);
786 let properties = ServiceBusConnectionStringProperties {
787 endpoint: Some(url::Url::parse(&endpoint).unwrap()),
788 entity_path: Some("HubName"),
789 shared_access_signature: None,
790 shared_access_key_name: Some("RootSharedAccessManagementKey"),
791 shared_access_key: Some("FaKe#$1324@@"),
792 };
793
794 let connection_string = properties.to_connection_string();
795 assert!(connection_string.is_err());
796 }
797 }
798
799 #[test]
800 fn to_connection_string_returns_ok_with_servicebus_endpoint_scheme() {
801 let endpoint = "sb://myhub.servicebus.windows.net";
802 let properties = ServiceBusConnectionStringProperties {
803 endpoint: Some(url::Url::parse(endpoint).unwrap()),
804 entity_path: Some("HubName"),
805 shared_access_signature: None,
806 shared_access_key_name: Some("RootSharedAccessManagementKey"),
807 shared_access_key: Some("FaKe#$1324@@"),
808 };
809
810 let connection_string = properties.to_connection_string();
811 assert!(connection_string.is_ok());
812 let connection_string = connection_string.unwrap();
813
814 let parsed = ServiceBusConnectionStringProperties::parse(&connection_string);
815 assert!(parsed.is_ok());
816 assert_eq!(properties, parsed.unwrap());
817 }
818
819 #[test]
820 fn to_connection_string_allows_shared_access_key_authorization() {
821 let fake_connection = "Endpoint=sb://not-real.servicebus.windows.net/;SharedAccessKeyName=DummyKey;SharedAccessKey=[not_real]";
822 let properties = ServiceBusConnectionStringProperties::parse(fake_connection).unwrap();
823
824 assert!(properties.to_connection_string().is_ok());
825 }
826
827 #[test]
828 fn to_connection_string_allows_shared_access_signature_authorization() {
829 let fake_connection =
830 "Endpoint=sb://not-real.servicebus.windows.net/;SharedAccessSignature=[not_real]";
831 let properties = ServiceBusConnectionStringProperties::parse(fake_connection).unwrap();
832
833 assert!(properties.to_connection_string().is_ok());
834 }
835}