After reviewing feedback from both @toto2 and @RandallSchultz, and after working through other issues related to clients using the scala compiler's provided copy method, I have made a number of changes.
Changes:
- Updated the explicit companion object's class name generator to properly account for class names starting with an underscore ("_")
- Added a
produceValidationRuntimeExceptions method to separate validation implementation from validation interface - simplifies future automated code generation
- Added
private to the case class constructor thereby requiring all instances be created via the explicit companion object's construct methods - eases facilitating future instance caching strategies
- Added a
constructCopy method to the case class to safely reproduce the compiler generated copy method's functionality - properly routes instantiation through the explicit companion object's construct method
- Added a
copy method to the case class to suppress the compiler generated copy method
- Implemented the
copy method with an UnsupportedOperationException directing the client to use the constructCopy method
A big thank you to both @toto2 and @RandallSchultz for helping me push through the noise related to making the case class constructor private.
Here is what the code now looks like with these above changes:
type StreetSecondaryParameters = (String, Option[String])
object StreetSecondary extends ((String, Option[String]) => StreetSecondary) {
override def toString =
getClass.getName.split("\\$").reverse.dropWhile(x => {val char = x.take(1).head; !((char == '_') || char.isLetter)}).head
def validate(values: StreetSecondaryParameters): List[RuntimeException] =
produceValidationRuntimeExceptions(values)
def construct(values: StreetSecondaryParameters): Either[List[RuntimeException], StreetSecondary] = {
val exceptions = validate(values)
if (exceptions.nonEmpty) Left(exceptions)
else Right(StreetSecondary.tupled(values))
}
def validate(designator: String, value: Option[String]): List[RuntimeException] =
validate((designator, value))
def construct(designator: String, value: Option[String]): Either[List[RuntimeException], StreetSecondary] =
construct((designator, value))
def apply(values: StreetSecondaryParameters): StreetSecondary =
StreetSecondary.tupled(values)
//
def produceValidationRuntimeExceptions(values: StreetSecondaryParameters): List[RuntimeException] = {
val (designator, value) =
values
List(
if (!designator.forall(char => ((char == '#') || char.isLetter)))
Some(new IllegalStateException(s"designator [$designator] must only contain characters from [#,A-Za-z]"))
else None
, if (value.isDefined && (value.get.head == ' '))
Some(new IllegalStateException(s"if defined, value [${value.get}] must not start with a space"))
else None
).flatten
}
}
case class StreetSecondary private (designator: String, value: Option[String]) {
def copy(designator: String = designator, value: Option[String] = value): StreetSecondary =
throw new UnsupportedOperationException("use constructCopy instead")
def constructCopy(designator: String = designator, value: Option[String] = value): Either[List[RuntimeException], StreetSecondary] =
StreetSecondary.construct(designator, value)
}