Hacker News new | ask | show | jobs
by maxdeviant 2479 days ago
The problem I have with this style of validation is that it doesn't "make illegal states unrepresentable". If the validation is not run then it is still possible for instances of `ContactData` that violate the business rules to enter the system.

  > Our reservation business requires that contact data entities are only accepted if all of the following conditions are satisfied:
  >
  > - The e-mail address is valid
  > - The phone number is valid
  > - Either e-mail address, or phone number, or both are present
In this case, all of the above rules can be encoded into the type system itself. Here's how I would approach this particular problem:

  struct PhoneNumber(String);
  
  impl PhoneNumber {
      fn new(value: String) -> Result<Self, ValidationError> {
          if is_valid_phone_number(value) {
              Ok(Self(value))
          } else {
              Err(ValidationError)
          }
      }
  }
  
  struct EmailAddress(String);
  
  impl EmailAddress {
      fn new(value: String) -> Result<Self, ValidationError> {
          if is_valid_email_address(value) {
              Ok(Self(value))
          } else {
              Err(ValidationError)
          }
      }
  }
  
  enum ContactData {
      PhoneOnly(PhoneNumber),
      EmailOnly(EmailAddress),
      PhoneAndEmail {
          phone: PhoneNumber,
          email: EmailAddress
      }
  }
This approach, when applied alongside Rust's modules and visibility modifiers, would make it impossible for any of these types to be in an invalid state.