From c61632da3d81f3fcccdba6213505a5cbdb098d5a Mon Sep 17 00:00:00 2001 From: Pierre Dal-Pra Date: Thu, 31 Oct 2019 22:10:35 +0100 Subject: [PATCH 1/3] Fix typos in EnumSpec --- enumeratum-core/src/test/scala/enumeratum/EnumSpec.scala | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/enumeratum-core/src/test/scala/enumeratum/EnumSpec.scala b/enumeratum-core/src/test/scala/enumeratum/EnumSpec.scala index 71a36d1e..6dc97333 100644 --- a/enumeratum-core/src/test/scala/enumeratum/EnumSpec.scala +++ b/enumeratum-core/src/test/scala/enumeratum/EnumSpec.scala @@ -100,7 +100,7 @@ class EnumSpec extends FunSpec with Matchers { DummyEnum.withNameUppercaseOnly("HI") should be(Hi) } - it("should return None for not uppercase but case insensitive values") { + it("should throw an error for not uppercase but case insensitive values") { intercept[NoSuchElementException] { DummyEnum.withNameUppercaseOnly("Hello") } @@ -144,7 +144,7 @@ class EnumSpec extends FunSpec with Matchers { DummyEnum.withNameLowercaseOnly("hi") should be(Hi) } - it("should return None for not uppercase but case insensitive values") { + it("should throw an error for not lowercase but case insensitive values") { intercept[NoSuchElementException] { DummyEnum.withNameLowercaseOnly("Hello") } @@ -170,13 +170,13 @@ class EnumSpec extends FunSpec with Matchers { DummyEnum.withNameLowercaseOnlyOption("hi").value should be(Hi) } - it("should return None for not uppercase but case insensitive values") { + it("should return None for not lowercase but case insensitive values") { DummyEnum.withNameLowercaseOnlyOption("Hello") should be(None) DummyEnum.withNameLowercaseOnlyOption("GoodBye") should be(None) DummyEnum.withNameLowercaseOnlyOption("Hi") should be(None) } - it("should throw an error otherwise") { + it("should return None otherwise") { DummyEnum.withNameLowercaseOnlyOption("bbeeeech") should be(None) } } From 21ee73190af268dfb200dfd7db6f6c6365505780 Mon Sep 17 00:00:00 2001 From: Pierre Dal-Pra Date: Fri, 1 Nov 2019 11:44:04 +0100 Subject: [PATCH 2/3] Add withName* methods variants returning an Either --- .../src/main/scala/enumeratum/Enum.scala | 28 +++++++ .../main/scala/enumeratum/NoSuchMember.scala | 10 +++ .../src/test/scala/enumeratum/EnumSpec.scala | 77 +++++++++++++++++++ 3 files changed, 115 insertions(+) create mode 100644 enumeratum-core/src/main/scala/enumeratum/NoSuchMember.scala diff --git a/enumeratum-core/src/main/scala/enumeratum/Enum.scala b/enumeratum-core/src/main/scala/enumeratum/Enum.scala index 34448b0c..56085721 100644 --- a/enumeratum-core/src/main/scala/enumeratum/Enum.scala +++ b/enumeratum-core/src/main/scala/enumeratum/Enum.scala @@ -85,6 +85,13 @@ trait Enum[A <: EnumEntry] { */ def withNameOption(name: String): Option[A] = namesToValuesMap.get(name) + /** + * Returns an [[Right[A]]] for a given name, or a [[Left[String]]] if the name does not match any of the values' + * .entryName values. + */ + def withNameEither(name: String): Either[NoSuchMember[EnumEntry], A] = + namesToValuesMap.get(name).toRight(NoSuchMember(name, values)) + /** * Tries to get an [[A]] by the supplied name. The name corresponds to the .name * of the case objects implementing [[A]], disregarding case @@ -139,6 +146,27 @@ trait Enum[A <: EnumEntry] { def withNameLowercaseOnlyOption(name: String): Option[A] = lowerCaseNamesToValuesMap.get(name) + /** + * Returns an [[Right[A]]] for a given name, or a [[Left[String]]] if the name does not match any of the values' + * .entryName values, disregarding case. + */ + def withNameInsensitiveEither(name: String): Either[String, A] = + lowerCaseNamesToValuesMap.get(name.toLowerCase).toRight(buildNotFoundMessage(name)) + + /** + * Returns an [[Right[A]]] for a given name, or a [[Left[String]]] if the name does not match any of the values' + * .entryName values, disregarding case. + */ + def withNameUppercaseOnlyEither(name: String): Either[String, A] = + upperCaseNameValuesToMap.get(name).toRight(buildNotFoundMessage(name)) + + /** + * Returns an [[Right[A]]] for a given name, or a [[Left[String]]] if the name does not match any of the values' + * .entryName values, disregarding case. + */ + def withNameLowercaseOnlyEither(name: String): Either[String, A] = + lowerCaseNamesToValuesMap.get(name).toRight(buildNotFoundMessage(name)) + /** * Returns the index number of the member passed in the values picked up by this enum * diff --git a/enumeratum-core/src/main/scala/enumeratum/NoSuchMember.scala b/enumeratum-core/src/main/scala/enumeratum/NoSuchMember.scala new file mode 100644 index 00000000..588a34b0 --- /dev/null +++ b/enumeratum-core/src/main/scala/enumeratum/NoSuchMember.scala @@ -0,0 +1,10 @@ +package enumeratum + +import scala.util.control.NoStackTrace + +final case class NoSuchMember[A <: EnumEntry](notFoundName: String, enumValues: IndexedSeq[A]) + extends NoSuchElementException + with NoStackTrace { + override def getMessage: String = + s"$notFoundName is not a member of Enum (${enumValues.map(_.entryName).mkString(", ")})" +} diff --git a/enumeratum-core/src/test/scala/enumeratum/EnumSpec.scala b/enumeratum-core/src/test/scala/enumeratum/EnumSpec.scala index 6dc97333..a1d8b424 100644 --- a/enumeratum-core/src/test/scala/enumeratum/EnumSpec.scala +++ b/enumeratum-core/src/test/scala/enumeratum/EnumSpec.scala @@ -55,6 +55,20 @@ class EnumSpec extends FunSpec with Matchers { } + describe("#withNameEither") { + + it("should return the proper object when passed the proper string") { + DummyEnum.withNameEither("Hello") should be(Right(Hello)) + DummyEnum.withNameEither("GoodBye") should be(Right(GoodBye)) + DummyEnum.withNameEither("Hi") should be(Right(Hi)) + } + + it("should return Left otherwise") { + DummyEnum.withNameEither("hello") shouldBe Left(NoSuchMember("hello", DummyEnum.values)) + } + + } + describe("#withNameInsensitive") { it("should return the proper object when passed the proper string, disregarding cases") { @@ -93,6 +107,25 @@ class EnumSpec extends FunSpec with Matchers { } + describe("#withNameInsensitiveEither") { + + it("should return the proper object when passed the proper string, disregarding cases") { + DummyEnum.withNameInsensitiveEither("Hello") should be(Right(Hello)) + DummyEnum.withNameInsensitiveEither("hello") should be(Right(Hello)) + DummyEnum.withNameInsensitiveEither("GoodBye") should be(Right(GoodBye)) + DummyEnum.withNameInsensitiveEither("goodBye") should be(Right(GoodBye)) + DummyEnum.withNameInsensitiveEither("gOodbye") should be(Right(GoodBye)) + DummyEnum.withNameInsensitiveEither("Hi") should be(Right(Hi)) + DummyEnum.withNameInsensitiveEither("hI") should be(Right(Hi)) + } + + it("should return Left otherwise") { + DummyEnum.withNameInsensitiveEither("bbeeeech") shouldBe Left( + "bbeeeech is not a member of Enum (Hello, GoodBye, Hi)") + } + + } + describe("#withNameUppercaseOnly") { it("should return the proper object when passed the proper string, transforming to upper case first") { DummyEnum.withNameUppercaseOnly("HELLO") should be(Hello) @@ -137,6 +170,28 @@ class EnumSpec extends FunSpec with Matchers { } } + describe("#withNameUppercaseOnlyEither") { + it("should return the proper object when passed the proper string, transforming to upper case first") { + DummyEnum.withNameUppercaseOnlyEither("HELLO") should be(Right(Hello)) + DummyEnum.withNameUppercaseOnlyEither("GOODBYE") should be(Right(GoodBye)) + DummyEnum.withNameUppercaseOnlyEither("HI") should be(Right(Hi)) + } + + it("should return Left for not uppercase but case insensitive values") { + DummyEnum.withNameUppercaseOnlyEither("Hello") should be( + Left("Hello is not a member of Enum (Hello, GoodBye, Hi)")) + DummyEnum.withNameUppercaseOnlyEither("GoodBye") should be( + Left("GoodBye is not a member of Enum (Hello, GoodBye, Hi)")) + DummyEnum.withNameUppercaseOnlyEither("Hi") should be( + Left("Hi is not a member of Enum (Hello, GoodBye, Hi)")) + } + + it("should return Left otherwise") { + DummyEnum.withNameUppercaseOnlyEither("bbeeeech") should be( + Left("bbeeeech is not a member of Enum (Hello, GoodBye, Hi)")) + } + } + describe("#withNameLowercaseOnly") { it("should return the proper object when passed the proper string, transforming to lower case first") { DummyEnum.withNameLowercaseOnly("hello") should be(Hello) @@ -180,6 +235,28 @@ class EnumSpec extends FunSpec with Matchers { DummyEnum.withNameLowercaseOnlyOption("bbeeeech") should be(None) } } + + describe("#withNameLowercaseOnlyEither") { + it("should return the proper object when passed the proper string, transforming to lower case first") { + DummyEnum.withNameLowercaseOnlyEither("hello") should be(Right(Hello)) + DummyEnum.withNameLowercaseOnlyEither("goodbye") should be(Right(GoodBye)) + DummyEnum.withNameLowercaseOnlyEither("hi") should be(Right(Hi)) + } + + it("should return Left for not lowercase but case insensitive values") { + DummyEnum.withNameLowercaseOnlyEither("Hello") should be( + Left("Hello is not a member of Enum (Hello, GoodBye, Hi)")) + DummyEnum.withNameLowercaseOnlyEither("GoodBye") should be( + Left("GoodBye is not a member of Enum (Hello, GoodBye, Hi)")) + DummyEnum.withNameLowercaseOnlyEither("Hi") should be( + Left("Hi is not a member of Enum (Hello, GoodBye, Hi)")) + } + + it("should return Left otherwise") { + DummyEnum.withNameLowercaseOnlyEither("bbeeeech") should be( + Left("bbeeeech is not a member of Enum (Hello, GoodBye, Hi)")) + } + } } describe("when a sealed trait is wrapped in another object") { From c8a50f440af5863994488609e19726d2f89d55b7 Mon Sep 17 00:00:00 2001 From: Pierre Dal-Pra Date: Sun, 3 Nov 2019 13:35:55 +0100 Subject: [PATCH 3/3] Add withValue* variant returning an Either to ValueEnum --- .../scala/enumeratum/values/NoSuchMember.scala | 11 +++++++++++ .../main/scala/enumeratum/values/ValueEnum.scala | 8 ++++++++ .../enumeratum/values/ValueEnumHelpers.scala | 16 ++++++++++++++++ 3 files changed, 35 insertions(+) create mode 100644 enumeratum-core/src/main/scala/enumeratum/values/NoSuchMember.scala diff --git a/enumeratum-core/src/main/scala/enumeratum/values/NoSuchMember.scala b/enumeratum-core/src/main/scala/enumeratum/values/NoSuchMember.scala new file mode 100644 index 00000000..78842ba3 --- /dev/null +++ b/enumeratum-core/src/main/scala/enumeratum/values/NoSuchMember.scala @@ -0,0 +1,11 @@ +package enumeratum.values + +import scala.util.control.NoStackTrace + +final case class NoSuchMember[ValueType, A <: ValueEnumEntry[ValueType]](notFoundValue: ValueType, + enumValues: IndexedSeq[A]) + extends NoSuchElementException + with NoStackTrace { + override def getMessage: String = + s"$notFoundValue is not a member of ValueEnum (${enumValues.map(_.value).mkString(", ")})" +} diff --git a/enumeratum-core/src/main/scala/enumeratum/values/ValueEnum.scala b/enumeratum-core/src/main/scala/enumeratum/values/ValueEnum.scala index f96695de..332756ee 100644 --- a/enumeratum-core/src/main/scala/enumeratum/values/ValueEnum.scala +++ b/enumeratum-core/src/main/scala/enumeratum/values/ValueEnum.scala @@ -62,6 +62,14 @@ sealed trait ValueEnum[ValueType, EntryType <: ValueEnumEntry[ValueType]] { */ def withValueOpt(i: ValueType): Option[EntryType] = valuesToEntriesMap.get(i) + /** + * Returns an [[Right[EntryType]]] for a given value, or a [[Left[String]]] if the value does not match any of the values' + * `.value` values. + */ + def withValueEither( + i: ValueType): Either[NoSuchMember[ValueType, ValueEnumEntry[ValueType]], EntryType] = + valuesToEntriesMap.get(i).toRight(NoSuchMember(i, values)) + private lazy val existingEntriesString = values.map(_.value).mkString(", ") private def buildNotFoundMessage(i: ValueType): String = { diff --git a/enumeratum-core/src/test/scala/enumeratum/values/ValueEnumHelpers.scala b/enumeratum-core/src/test/scala/enumeratum/values/ValueEnumHelpers.scala index 5d6ef414..cb46bd4a 100644 --- a/enumeratum-core/src/test/scala/enumeratum/values/ValueEnumHelpers.scala +++ b/enumeratum-core/src/test/scala/enumeratum/values/ValueEnumHelpers.scala @@ -71,6 +71,22 @@ trait ValueEnumHelpers { this: FunSpec with Matchers => } + describe("withValueEither") { + + it("should return Right(entry) that match the value") { + enum.values.foreach { entry => + enum.withValueEither(entry.value) shouldBe Right(entry) + } + } + + it("should return Left when given values that do not map to any entries") { + invalidValues.foreach { invalid => + enum.withValueEither(invalid) shouldBe Left(NoSuchMember(invalid, enum.values)) + } + } + + } + describe("in") { it("should return false if given an empty list") {