value.rs (23637B)
1 //! Validated service-neutral configuration leaf values. 2 3 use core::fmt; 4 use core::marker::PhantomData; 5 use core::str::FromStr; 6 use core::time::Duration; 7 use std::error::Error; 8 9 use serde::de::Error as _; 10 use serde::{Deserialize, Deserializer, Serialize, Serializer}; 11 12 use crate::operations::{ 13 OperationsBindPolicy, OperationsConfigError, OperationsListenAddress, OperationsListenerConfig, 14 OperationsTransportLimits, 15 }; 16 17 const NANOSECONDS_PER_MICROSECOND: u64 = 1_000; 18 const NANOSECONDS_PER_MILLISECOND: u64 = 1_000_000; 19 const NANOSECONDS_PER_SECOND: u64 = 1_000_000_000; 20 const NANOSECONDS_PER_MINUTE: u64 = 60 * NANOSECONDS_PER_SECOND; 21 const NANOSECONDS_PER_HOUR: u64 = 60 * NANOSECONDS_PER_MINUTE; 22 const NANOSECONDS_PER_DAY: u64 = 24 * NANOSECONDS_PER_HOUR; 23 24 const BYTES_PER_KIBIBYTE: u64 = 1024; 25 const BYTES_PER_MEBIBYTE: u64 = 1024 * BYTES_PER_KIBIBYTE; 26 const BYTES_PER_GIBIBYTE: u64 = 1024 * BYTES_PER_MEBIBYTE; 27 const BYTES_PER_TEBIBYTE: u64 = 1024 * BYTES_PER_GIBIBYTE; 28 29 /// A positive duration represented by at most `u64::MAX` nanoseconds. 30 #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] 31 pub struct PositiveDuration(Duration); 32 33 impl PositiveDuration { 34 /// Validates a positive, canonically representable duration. 35 pub fn new(value: Duration) -> Result<Self, PositiveDurationError> { 36 if value.is_zero() { 37 return Err(PositiveDurationError::Zero); 38 } 39 if value.as_nanos() > u128::from(u64::MAX) { 40 return Err(PositiveDurationError::Overflow); 41 } 42 Ok(Self(value)) 43 } 44 45 /// Returns the validated standard duration. 46 #[must_use] 47 pub const fn duration(self) -> Duration { 48 self.0 49 } 50 51 /// Returns the exact total nanoseconds. 52 #[must_use] 53 pub fn nanoseconds(self) -> u64 { 54 u64::try_from(self.0.as_nanos()).expect("validated positive duration fits u64 nanoseconds") 55 } 56 } 57 58 impl FromStr for PositiveDuration { 59 type Err = PositiveDurationError; 60 61 fn from_str(value: &str) -> Result<Self, Self::Err> { 62 let nanoseconds = parse_human_quantity( 63 value, 64 &[ 65 ("ms", NANOSECONDS_PER_MILLISECOND), 66 ("us", NANOSECONDS_PER_MICROSECOND), 67 ("ns", 1), 68 ("d", NANOSECONDS_PER_DAY), 69 ("h", NANOSECONDS_PER_HOUR), 70 ("m", NANOSECONDS_PER_MINUTE), 71 ("s", NANOSECONDS_PER_SECOND), 72 ], 73 ) 74 .map_err(PositiveDurationError::from_quantity)?; 75 Self::new(Duration::from_nanos(nanoseconds)) 76 } 77 } 78 79 impl fmt::Display for PositiveDuration { 80 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 81 format_human_quantity( 82 formatter, 83 self.nanoseconds(), 84 &[ 85 ("d", NANOSECONDS_PER_DAY), 86 ("h", NANOSECONDS_PER_HOUR), 87 ("m", NANOSECONDS_PER_MINUTE), 88 ("s", NANOSECONDS_PER_SECOND), 89 ("ms", NANOSECONDS_PER_MILLISECOND), 90 ("us", NANOSECONDS_PER_MICROSECOND), 91 ("ns", 1), 92 ], 93 ) 94 } 95 } 96 97 impl Serialize for PositiveDuration { 98 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 99 where 100 S: Serializer, 101 { 102 serializer.collect_str(self) 103 } 104 } 105 106 impl<'de> Deserialize<'de> for PositiveDuration { 107 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 108 where 109 D: Deserializer<'de>, 110 { 111 deserialize_from_str(deserializer) 112 } 113 } 114 115 /// Safe positive-duration parse failure. 116 #[derive(Clone, Copy, Debug, PartialEq, Eq)] 117 pub enum PositiveDurationError { 118 Invalid, 119 Zero, 120 Overflow, 121 } 122 123 impl PositiveDurationError { 124 const fn from_quantity(error: HumanQuantityError) -> Self { 125 match error { 126 HumanQuantityError::Invalid => Self::Invalid, 127 HumanQuantityError::Zero => Self::Zero, 128 HumanQuantityError::Overflow => Self::Overflow, 129 } 130 } 131 } 132 133 impl fmt::Display for PositiveDurationError { 134 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 135 formatter.write_str("positive duration is invalid") 136 } 137 } 138 139 impl Error for PositiveDurationError {} 140 141 /// A positive exact byte quantity using canonical binary human units. 142 #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] 143 pub struct ByteLimit(u64); 144 145 impl ByteLimit { 146 /// Validates a positive byte quantity. 147 pub const fn new(bytes: u64) -> Result<Self, ByteLimitError> { 148 if bytes == 0 { 149 Err(ByteLimitError::Zero) 150 } else { 151 Ok(Self(bytes)) 152 } 153 } 154 155 /// Returns the exact byte quantity. 156 #[must_use] 157 pub const fn bytes(self) -> u64 { 158 self.0 159 } 160 } 161 162 impl FromStr for ByteLimit { 163 type Err = ByteLimitError; 164 165 fn from_str(value: &str) -> Result<Self, Self::Err> { 166 let bytes = parse_human_quantity( 167 value, 168 &[ 169 ("KiB", BYTES_PER_KIBIBYTE), 170 ("MiB", BYTES_PER_MEBIBYTE), 171 ("GiB", BYTES_PER_GIBIBYTE), 172 ("TiB", BYTES_PER_TEBIBYTE), 173 ("B", 1), 174 ], 175 ) 176 .map_err(ByteLimitError::from_quantity)?; 177 Self::new(bytes) 178 } 179 } 180 181 impl fmt::Display for ByteLimit { 182 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 183 format_human_quantity( 184 formatter, 185 self.bytes(), 186 &[ 187 ("TiB", BYTES_PER_TEBIBYTE), 188 ("GiB", BYTES_PER_GIBIBYTE), 189 ("MiB", BYTES_PER_MEBIBYTE), 190 ("KiB", BYTES_PER_KIBIBYTE), 191 ("B", 1), 192 ], 193 ) 194 } 195 } 196 197 impl Serialize for ByteLimit { 198 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 199 where 200 S: Serializer, 201 { 202 serializer.collect_str(self) 203 } 204 } 205 206 impl<'de> Deserialize<'de> for ByteLimit { 207 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 208 where 209 D: Deserializer<'de>, 210 { 211 deserialize_from_str(deserializer) 212 } 213 } 214 215 /// Safe byte-limit parse failure. 216 #[derive(Clone, Copy, Debug, PartialEq, Eq)] 217 pub enum ByteLimitError { 218 Invalid, 219 Zero, 220 Overflow, 221 } 222 223 impl ByteLimitError { 224 const fn from_quantity(error: HumanQuantityError) -> Self { 225 match error { 226 HumanQuantityError::Invalid => Self::Invalid, 227 HumanQuantityError::Zero => Self::Zero, 228 HumanQuantityError::Overflow => Self::Overflow, 229 } 230 } 231 } 232 233 impl fmt::Display for ByteLimitError { 234 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 235 formatter.write_str("byte limit is invalid") 236 } 237 } 238 239 impl Error for ByteLimitError {} 240 241 /// A positive count bounded by its compile-time service-owned maximum. 242 #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] 243 pub struct BoundedCount<const MAXIMUM: u32>(u32); 244 245 impl<const MAXIMUM: u32> BoundedCount<MAXIMUM> { 246 /// Validates one positive count against the compile-time maximum. 247 pub const fn new(value: u32) -> Result<Self, BoundedCountError> { 248 if MAXIMUM == 0 { 249 Err(BoundedCountError::InvalidMaximum) 250 } else if value == 0 { 251 Err(BoundedCountError::Zero) 252 } else if value > MAXIMUM { 253 Err(BoundedCountError::ExceedsMaximum) 254 } else { 255 Ok(Self(value)) 256 } 257 } 258 259 /// Returns the exact count. 260 #[must_use] 261 pub const fn value(self) -> u32 { 262 self.0 263 } 264 265 /// Returns the compile-time maximum. 266 #[must_use] 267 pub const fn maximum() -> u32 { 268 MAXIMUM 269 } 270 } 271 272 impl<const MAXIMUM: u32> fmt::Display for BoundedCount<MAXIMUM> { 273 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 274 self.0.fmt(formatter) 275 } 276 } 277 278 impl<const MAXIMUM: u32> Serialize for BoundedCount<MAXIMUM> { 279 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 280 where 281 S: Serializer, 282 { 283 serializer.serialize_u32(self.0) 284 } 285 } 286 287 impl<'de, const MAXIMUM: u32> Deserialize<'de> for BoundedCount<MAXIMUM> { 288 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 289 where 290 D: Deserializer<'de>, 291 { 292 let value = u64::deserialize(deserializer)?; 293 let value = u32::try_from(value) 294 .map_err(|_| D::Error::custom(BoundedCountError::ExceedsMaximum))?; 295 Self::new(value).map_err(D::Error::custom) 296 } 297 } 298 299 /// Safe bounded-count validation failure. 300 #[derive(Clone, Copy, Debug, PartialEq, Eq)] 301 pub enum BoundedCountError { 302 InvalidMaximum, 303 Zero, 304 ExceedsMaximum, 305 } 306 307 impl fmt::Display for BoundedCountError { 308 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 309 formatter.write_str("bounded count is outside its supported positive range") 310 } 311 } 312 313 impl Error for BoundedCountError {} 314 315 /// Closed v1 logging encoding for safe structured service logs. 316 #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] 317 pub enum LoggingFormat { 318 #[default] 319 Json, 320 } 321 322 impl FromStr for LoggingFormat { 323 type Err = LoggingFormatError; 324 325 fn from_str(value: &str) -> Result<Self, Self::Err> { 326 match value { 327 "json" => Ok(Self::Json), 328 _ => Err(LoggingFormatError::Unsupported), 329 } 330 } 331 } 332 333 impl fmt::Display for LoggingFormat { 334 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 335 formatter.write_str(match self { 336 Self::Json => "json", 337 }) 338 } 339 } 340 341 impl Serialize for LoggingFormat { 342 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 343 where 344 S: Serializer, 345 { 346 serializer.collect_str(self) 347 } 348 } 349 350 impl<'de> Deserialize<'de> for LoggingFormat { 351 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 352 where 353 D: Deserializer<'de>, 354 { 355 deserialize_from_str(deserializer) 356 } 357 } 358 359 /// Safe unsupported logging-format failure. 360 #[derive(Clone, Copy, Debug, PartialEq, Eq)] 361 pub enum LoggingFormatError { 362 Unsupported, 363 } 364 365 impl fmt::Display for LoggingFormatError { 366 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 367 formatter.write_str("logging format is unsupported") 368 } 369 } 370 371 impl Error for LoggingFormatError {} 372 373 /// Explicitly disabled or explicitly addressed operations binding. 374 #[derive(Clone, Copy, PartialEq, Eq)] 375 pub enum OptionalOperationsBind { 376 Disabled, 377 Listen(OperationsListenAddress), 378 } 379 380 impl OptionalOperationsBind { 381 /// Returns a disabled operations binding. 382 #[must_use] 383 pub const fn disabled() -> Self { 384 Self::Disabled 385 } 386 387 /// Returns an explicitly addressed operations binding. 388 #[must_use] 389 pub const fn listen(address: OperationsListenAddress) -> Self { 390 Self::Listen(address) 391 } 392 393 /// Returns whether an operations binding is explicitly enabled. 394 #[must_use] 395 pub const fn is_enabled(self) -> bool { 396 matches!(self, Self::Listen(_)) 397 } 398 399 /// Returns the selected address when enabled. 400 #[must_use] 401 pub const fn address(self) -> Option<OperationsListenAddress> { 402 match self { 403 Self::Disabled => None, 404 Self::Listen(address) => Some(address), 405 } 406 } 407 408 /// Applies the existing network-scope and transport-limit authority. 409 pub fn into_listener_config( 410 self, 411 bind_policy: OperationsBindPolicy, 412 limits: OperationsTransportLimits, 413 ) -> Result<OperationsListenerConfig, OperationsConfigError> { 414 match self { 415 Self::Disabled => Ok(OperationsListenerConfig::disabled()), 416 Self::Listen(address) => { 417 OperationsListenerConfig::enabled(address, bind_policy, limits) 418 } 419 } 420 } 421 } 422 423 impl Default for OptionalOperationsBind { 424 fn default() -> Self { 425 Self::disabled() 426 } 427 } 428 429 impl FromStr for OptionalOperationsBind { 430 type Err = OptionalOperationsBindError; 431 432 fn from_str(value: &str) -> Result<Self, Self::Err> { 433 if value == "disabled" { 434 return Ok(Self::Disabled); 435 } 436 value 437 .parse() 438 .map(Self::Listen) 439 .map_err(|_| OptionalOperationsBindError::Invalid) 440 } 441 } 442 443 impl fmt::Debug for OptionalOperationsBind { 444 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 445 formatter 446 .debug_struct("OptionalOperationsBind") 447 .field("enabled", &self.is_enabled()) 448 .field("listen", &self.address().map(|_| "[redacted]")) 449 .finish() 450 } 451 } 452 453 impl fmt::Display for OptionalOperationsBind { 454 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 455 match self { 456 Self::Disabled => formatter.write_str("disabled"), 457 Self::Listen(address) => address.fmt(formatter), 458 } 459 } 460 } 461 462 impl Serialize for OptionalOperationsBind { 463 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 464 where 465 S: Serializer, 466 { 467 serializer.collect_str(self) 468 } 469 } 470 471 impl<'de> Deserialize<'de> for OptionalOperationsBind { 472 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 473 where 474 D: Deserializer<'de>, 475 { 476 deserialize_from_str(deserializer) 477 } 478 } 479 480 /// Safe optional operations-bind parse failure. 481 #[derive(Clone, Copy, Debug, PartialEq, Eq)] 482 pub enum OptionalOperationsBindError { 483 Invalid, 484 } 485 486 impl fmt::Display for OptionalOperationsBindError { 487 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 488 formatter.write_str("optional operations bind is invalid") 489 } 490 } 491 492 impl Error for OptionalOperationsBindError {} 493 494 #[derive(Clone, Copy, Debug, PartialEq, Eq)] 495 enum HumanQuantityError { 496 Invalid, 497 Zero, 498 Overflow, 499 } 500 501 fn deserialize_from_str<'de, D, T>(deserializer: D) -> Result<T, D::Error> 502 where 503 D: Deserializer<'de>, 504 T: FromStr, 505 T::Err: fmt::Display, 506 { 507 struct FromStrVisitor<T>(PhantomData<T>); 508 509 impl<'de, T> serde::de::Visitor<'de> for FromStrVisitor<T> 510 where 511 T: FromStr, 512 T::Err: fmt::Display, 513 { 514 type Value = T; 515 516 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 517 formatter.write_str("a canonical bounded configuration string") 518 } 519 520 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E> 521 where 522 E: serde::de::Error, 523 { 524 value.parse().map_err(E::custom) 525 } 526 527 fn visit_string<E>(self, value: String) -> Result<Self::Value, E> 528 where 529 E: serde::de::Error, 530 { 531 value.parse().map_err(E::custom) 532 } 533 } 534 535 deserializer.deserialize_str(FromStrVisitor(PhantomData)) 536 } 537 538 fn parse_human_quantity(value: &str, units: &[(&str, u64)]) -> Result<u64, HumanQuantityError> { 539 let Some((number, multiplier)) = units.iter().find_map(|(unit, multiplier)| { 540 value.strip_suffix(unit).map(|number| (number, *multiplier)) 541 }) else { 542 return Err(HumanQuantityError::Invalid); 543 }; 544 if number.is_empty() 545 || !number.bytes().all(|byte| byte.is_ascii_digit()) 546 || (number.len() > 1 && number.starts_with('0')) 547 { 548 return Err(HumanQuantityError::Invalid); 549 } 550 let quantity = number 551 .parse::<u64>() 552 .map_err(|_| HumanQuantityError::Overflow)?; 553 if quantity == 0 { 554 return Err(HumanQuantityError::Zero); 555 } 556 quantity 557 .checked_mul(multiplier) 558 .ok_or(HumanQuantityError::Overflow) 559 } 560 561 fn format_human_quantity( 562 formatter: &mut fmt::Formatter<'_>, 563 value: u64, 564 units: &[(&str, u64)], 565 ) -> fmt::Result { 566 let (unit, multiplier) = units 567 .iter() 568 .find(|(_, multiplier)| value.is_multiple_of(*multiplier)) 569 .expect("unit inventory ends in multiplier one"); 570 write!(formatter, "{}{unit}", value / multiplier) 571 } 572 573 #[cfg(test)] 574 mod tests { 575 use serde::{Deserialize, Serialize}; 576 577 use super::*; 578 579 #[derive(Debug, Deserialize, Serialize, PartialEq, Eq)] 580 #[serde(deny_unknown_fields)] 581 struct Values { 582 duration: PositiveDuration, 583 bytes: ByteLimit, 584 count: BoundedCount<64>, 585 logging: LoggingFormat, 586 operations_bind: OptionalOperationsBind, 587 } 588 589 #[test] 590 fn human_duration_units_parse_and_display_canonically() { 591 for (source, nanoseconds, display) in [ 592 ("1ns", 1, "1ns"), 593 ("1us", 1_000, "1us"), 594 ("1ms", 1_000_000, "1ms"), 595 ("1500ms", 1_500_000_000, "1500ms"), 596 ("1s", 1_000_000_000, "1s"), 597 ("60s", 60_000_000_000, "1m"), 598 ("1m", 60_000_000_000, "1m"), 599 ("1h", 3_600_000_000_000, "1h"), 600 ("1d", 86_400_000_000_000, "1d"), 601 ] { 602 let value: PositiveDuration = source.parse().unwrap(); 603 assert_eq!(value.nanoseconds(), nanoseconds); 604 assert_eq!(value.to_string(), display); 605 assert_eq!(value.duration().as_nanos(), u128::from(nanoseconds)); 606 } 607 } 608 609 #[test] 610 fn duration_zero_invalid_and_overflow_inputs_fail_closed() { 611 for source in ["", "1", "s", "0s", "01s", "+1s", "-1s", "1.5s", "1S", " 1s"] { 612 assert!(source.parse::<PositiveDuration>().is_err(), "{source}"); 613 } 614 assert_eq!( 615 PositiveDuration::new(Duration::ZERO), 616 Err(PositiveDurationError::Zero) 617 ); 618 assert_eq!( 619 "18446744073709551616ns" 620 .parse::<PositiveDuration>() 621 .unwrap_err(), 622 PositiveDurationError::Overflow 623 ); 624 assert_eq!( 625 "18446744073709551615d" 626 .parse::<PositiveDuration>() 627 .unwrap_err(), 628 PositiveDurationError::Overflow 629 ); 630 assert_eq!( 631 PositiveDuration::new(Duration::MAX).unwrap_err(), 632 PositiveDurationError::Overflow 633 ); 634 let maximum = PositiveDuration::new(Duration::from_nanos(u64::MAX)).unwrap(); 635 assert_eq!(maximum.nanoseconds(), u64::MAX); 636 assert_eq!(maximum.to_string(), format!("{}ns", u64::MAX)); 637 } 638 639 #[test] 640 fn binary_byte_units_parse_and_display_canonically() { 641 for (source, bytes, display) in [ 642 ("1B", 1, "1B"), 643 ("1KiB", 1024, "1KiB"), 644 ("1536B", 1536, "1536B"), 645 ("1MiB", 1_048_576, "1MiB"), 646 ("1GiB", 1_073_741_824, "1GiB"), 647 ("1TiB", 1_099_511_627_776, "1TiB"), 648 ] { 649 let value: ByteLimit = source.parse().unwrap(); 650 assert_eq!(value.bytes(), bytes); 651 assert_eq!(value.to_string(), display); 652 } 653 } 654 655 #[test] 656 fn byte_zero_invalid_and_overflow_inputs_fail_closed() { 657 for source in [ 658 "", "1", "B", "0B", "01B", "+1B", "-1B", "1.5KiB", "1KB", " 1B", 659 ] { 660 assert!(source.parse::<ByteLimit>().is_err(), "{source}"); 661 } 662 assert_eq!(ByteLimit::new(0), Err(ByteLimitError::Zero)); 663 assert_eq!( 664 "18446744073709551616B".parse::<ByteLimit>().unwrap_err(), 665 ByteLimitError::Overflow 666 ); 667 assert_eq!( 668 "18446744073709551615KiB".parse::<ByteLimit>().unwrap_err(), 669 ByteLimitError::Overflow 670 ); 671 let maximum = ByteLimit::new(u64::MAX).unwrap(); 672 assert_eq!(maximum.bytes(), u64::MAX); 673 assert_eq!(maximum.to_string(), format!("{}B", u64::MAX)); 674 } 675 676 #[test] 677 fn bounded_count_rejects_zero_invalid_maximum_overflow_and_just_over() { 678 assert_eq!(BoundedCount::<64>::new(1).unwrap().value(), 1); 679 assert_eq!(BoundedCount::<64>::new(64).unwrap().value(), 64); 680 assert_eq!(BoundedCount::<64>::maximum(), 64); 681 assert_eq!( 682 BoundedCount::<64>::new(0).unwrap_err(), 683 BoundedCountError::Zero 684 ); 685 assert_eq!( 686 BoundedCount::<64>::new(65).unwrap_err(), 687 BoundedCountError::ExceedsMaximum 688 ); 689 assert_eq!( 690 BoundedCount::<0>::new(1).unwrap_err(), 691 BoundedCountError::InvalidMaximum 692 ); 693 assert!(toml::from_str::<Values>( 694 "duration='1s'\nbytes='1KiB'\ncount=4294967296\nlogging='json'\noperations_bind='disabled'", 695 ) 696 .is_err()); 697 } 698 699 #[test] 700 fn serde_and_display_are_exact_for_every_common_leaf() { 701 let source = concat!( 702 "duration = \"2m\"\n", 703 "bytes = \"8MiB\"\n", 704 "count = 32\n", 705 "logging = \"json\"\n", 706 "operations_bind = \"127.0.0.1:9100\"\n", 707 ); 708 let values: Values = toml::from_str(source).unwrap(); 709 assert_eq!(values.duration.to_string(), "2m"); 710 assert_eq!(values.bytes.to_string(), "8MiB"); 711 assert_eq!(values.count.to_string(), "32"); 712 assert_eq!(values.logging.to_string(), "json"); 713 assert_eq!(values.operations_bind.to_string(), "127.0.0.1:9100"); 714 assert_eq!(toml::to_string(&values).unwrap(), source); 715 } 716 717 #[test] 718 fn logging_format_is_closed_to_structured_json() { 719 assert_eq!(LoggingFormat::default(), LoggingFormat::Json); 720 assert_eq!("json".parse(), Ok(LoggingFormat::Json)); 721 for source in ["", "JSON", "text", "pretty", "compact"] { 722 assert_eq!( 723 source.parse::<LoggingFormat>(), 724 Err(LoggingFormatError::Unsupported) 725 ); 726 } 727 } 728 729 #[test] 730 fn optional_operations_bind_is_explicit_redacted_and_policy_checked() { 731 assert_eq!( 732 OptionalOperationsBind::disabled(), 733 OptionalOperationsBind::default() 734 ); 735 let disabled: OptionalOperationsBind = "disabled".parse().unwrap(); 736 assert!(!disabled.is_enabled()); 737 assert_eq!(disabled.address(), None); 738 assert_eq!(disabled.to_string(), "disabled"); 739 assert_eq!( 740 disabled 741 .into_listener_config( 742 OperationsBindPolicy::LoopbackOnly, 743 OperationsTransportLimits::DEFAULT, 744 ) 745 .unwrap(), 746 OperationsListenerConfig::disabled() 747 ); 748 749 let loopback: OptionalOperationsBind = "127.0.0.1:9100".parse().unwrap(); 750 assert_eq!( 751 OptionalOperationsBind::listen(loopback.address().unwrap()), 752 loopback 753 ); 754 assert!(loopback.is_enabled()); 755 assert_eq!(loopback.to_string(), "127.0.0.1:9100"); 756 assert!(!format!("{loopback:?}").contains("127.0.0.1")); 757 assert!( 758 loopback 759 .into_listener_config( 760 OperationsBindPolicy::LoopbackOnly, 761 OperationsTransportLimits::DEFAULT, 762 ) 763 .is_ok() 764 ); 765 766 let public: OptionalOperationsBind = "0.0.0.0:9100".parse().unwrap(); 767 assert!( 768 public 769 .into_listener_config( 770 OperationsBindPolicy::LoopbackOnly, 771 OperationsTransportLimits::DEFAULT, 772 ) 773 .is_err() 774 ); 775 assert!( 776 public 777 .into_listener_config( 778 OperationsBindPolicy::Public, 779 OperationsTransportLimits::DEFAULT, 780 ) 781 .is_ok() 782 ); 783 784 for source in ["", "Disabled", "127.0.0.1:0", "localhost:9100"] { 785 assert_eq!( 786 source.parse::<OptionalOperationsBind>(), 787 Err(OptionalOperationsBindError::Invalid) 788 ); 789 } 790 } 791 }