diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f98b3e7dc9..b8c91ce440 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -7,22 +7,37 @@ Note that ``RB_ID=#`` and ``PHAB_ID=#`` correspond to associated message in comm Unreleased ---------- +Added +~~~~~ + +* finatra-jackson: Add ability to bypass case class validation using the + `NullValidationFinatraJacksonModule`. ``PHAB_ID=D307795`` + +* inject-app: Add `c.t.inject.app.DtabResolution` to help users apply supplemental Dtabs added by + setting the dtab.add flag. This will append the supplemental Dtabs to the + Dtab.base in a premain function. ``PHAB_ID=D303813`` + +Changed +~~~~~~~ + * inject-app: Move override of `com.twitter.app.App#failfastOnFlagsNotParsed` up from `c.t.inject.server.TwitterServer` to `com.twitter.inject.app.App` such that all Finatra-based applications default to this behavior. ``PHAB_ID=D307858`` -* inject-app|server: Fix capturing of flag ordering from Modules for adding to the App's `c.t.app.Flags` +* inject-app|server: change capturing of flag ordering from Modules for adding to the App's `c.t.app.Flags` instance to match the semantics of directly calling `c.t.app.Flags#add`. Prefer `AtomicBoolean` instances over where we currently use mutable `Boolean` instances in `c.t.inject.app.App`, `c.t.inject.app.TestInjector`, and `c.t.inject.server.EmbeddedTwitterServer`. ``PHAB_ID=D306897`` -* inject-app: Add `c.t.inject.app.DtabResolution` to help users apply supplemental Dtabs added by - setting the dtab.add flag. This will append the supplemental Dtabs to the - Dtab.base in a premain function. ``PHAB_ID=D303813`` - * finatra-examples: Update "twitter-clone" example to use `Dtabs` instead of the deprecated `resolverMap`. Move the "hello-world" example to "http-server". ``PHAB_ID=D303813`` +Fixed +~~~~~ + +Closed +~~~~~~ + 19.4.0 ------ diff --git a/doc/src/sphinx/user-guide/json/validations.rst b/doc/src/sphinx/user-guide/json/validations.rst index a700dd515b..4fd8baf07a 100644 --- a/doc/src/sphinx/user-guide/json/validations.rst +++ b/doc/src/sphinx/user-guide/json/validations.rst @@ -49,3 +49,21 @@ Eg., "nsfw: 'abc' is not a valid boolean" ] } + +Bypassing Validation +-------------------- + +You may desire to execute validation for specific case classes in certain scenarios, but bypass validation in others. +For example, you may want to validate a `POST` request on the write path and store the JSON results somewhere, but +bypass validating that same JSON for a `GET` request on the read path. + +You can create a `FinatraObjectMapper `__ +that will bypass validation like this: + +.. code:: scala + + def create(injector: Injector = null): FinatraObjectMapper = { + val jacksonModule = NullValidationFinatraJacksonModule + new FinatraObjectMapper( + jacksonModule.provideScalaObjectMapper(injector)) + } diff --git a/jackson/src/main/scala/com/twitter/finatra/json/internal/caseclass/validation/CaseClassValidator.scala b/jackson/src/main/scala/com/twitter/finatra/json/internal/caseclass/validation/CaseClassValidator.scala index 46a7b76ed7..8ed19704ff 100644 --- a/jackson/src/main/scala/com/twitter/finatra/json/internal/caseclass/validation/CaseClassValidator.scala +++ b/jackson/src/main/scala/com/twitter/finatra/json/internal/caseclass/validation/CaseClassValidator.scala @@ -6,7 +6,7 @@ import java.lang.annotation.Annotation /** * Trait for defining a validator that will be triggered during Case Class validation. */ -private[finatra] trait CaseClassValidator { +private[json] trait CaseClassValidator { def validateField[V]( fieldValue: V, diff --git a/jackson/src/main/scala/com/twitter/finatra/json/internal/caseclass/validation/DefaultValidationProvider.scala b/jackson/src/main/scala/com/twitter/finatra/json/internal/caseclass/validation/DefaultValidationProvider.scala index 1a7d6d2f0b..dee99bbe3c 100644 --- a/jackson/src/main/scala/com/twitter/finatra/json/internal/caseclass/validation/DefaultValidationProvider.scala +++ b/jackson/src/main/scala/com/twitter/finatra/json/internal/caseclass/validation/DefaultValidationProvider.scala @@ -2,7 +2,7 @@ package com.twitter.finatra.json.internal.caseclass.validation import com.twitter.finatra.validation.ValidationMessageResolver -private[finatra] object DefaultValidationProvider extends ValidationProvider { +private[json] object DefaultValidationProvider extends ValidationProvider { override def apply(): CaseClassValidator = { val messageResolver = new ValidationMessageResolver diff --git a/jackson/src/main/scala/com/twitter/finatra/json/internal/caseclass/validation/NullCaseClassValidator.scala b/jackson/src/main/scala/com/twitter/finatra/json/internal/caseclass/validation/NullCaseClassValidator.scala new file mode 100644 index 0000000000..8003686a2c --- /dev/null +++ b/jackson/src/main/scala/com/twitter/finatra/json/internal/caseclass/validation/NullCaseClassValidator.scala @@ -0,0 +1,18 @@ +package com.twitter.finatra.json.internal.caseclass.validation + +import com.twitter.finatra.validation.ValidationResult +import java.lang.annotation.Annotation + +/** + * No-op validator, which will treat any field values as acceptable during + * Finatra Validation of annotations. + */ +private[json] object NullCaseClassValidator extends CaseClassValidator { + + override def validateField[V]( + fieldValue: V, + fieldValidationAnnotations: Seq[Annotation] + ): Seq[ValidationResult] = Seq.empty + + override def validateObject(obj: Any): Seq[ValidationResult] = Seq.empty +} diff --git a/jackson/src/main/scala/com/twitter/finatra/json/internal/caseclass/validation/NullValidationProvider.scala b/jackson/src/main/scala/com/twitter/finatra/json/internal/caseclass/validation/NullValidationProvider.scala new file mode 100644 index 0000000000..ed44969938 --- /dev/null +++ b/jackson/src/main/scala/com/twitter/finatra/json/internal/caseclass/validation/NullValidationProvider.scala @@ -0,0 +1,8 @@ +package com.twitter.finatra.json.internal.caseclass.validation + +/** + * Provides a validation bypass for case class validation. + */ +private[json] object NullValidationProvider extends ValidationProvider { + override def apply(): CaseClassValidator = NullCaseClassValidator +} diff --git a/jackson/src/main/scala/com/twitter/finatra/json/internal/caseclass/validation/ValidationProvider.scala b/jackson/src/main/scala/com/twitter/finatra/json/internal/caseclass/validation/ValidationProvider.scala index 517637c2a2..029918485c 100644 --- a/jackson/src/main/scala/com/twitter/finatra/json/internal/caseclass/validation/ValidationProvider.scala +++ b/jackson/src/main/scala/com/twitter/finatra/json/internal/caseclass/validation/ValidationProvider.scala @@ -3,7 +3,7 @@ package com.twitter.finatra.json.internal.caseclass.validation /** * Trait that defines a factory for returning a CaseClassValidator. */ -private[finatra] trait ValidationProvider { +private[json] trait ValidationProvider { /** * Return a CaseClassValidator instance that will be used to provide validation against diff --git a/jackson/src/main/scala/com/twitter/finatra/json/internal/caseclass/validation/validators/CountryCodeValidator.scala b/jackson/src/main/scala/com/twitter/finatra/json/internal/caseclass/validation/validators/CountryCodeValidator.scala index 675f4ec49c..53938faa72 100644 --- a/jackson/src/main/scala/com/twitter/finatra/json/internal/caseclass/validation/validators/CountryCodeValidator.scala +++ b/jackson/src/main/scala/com/twitter/finatra/json/internal/caseclass/validation/validators/CountryCodeValidator.scala @@ -10,7 +10,7 @@ import com.twitter.finatra.validation.{ } import java.util.Locale -private[finatra] object CountryCodeValidator { +private[json] object CountryCodeValidator { def errorMessage(resolver: ValidationMessageResolver, value: Any) = { @@ -29,7 +29,7 @@ private[finatra] object CountryCodeValidator { } } -private[finatra] class CountryCodeValidator( +private[json] class CountryCodeValidator( validationMessageResolver: ValidationMessageResolver, annotation: CountryCode ) extends Validator[CountryCode, Any](validationMessageResolver, annotation) { diff --git a/jackson/src/main/scala/com/twitter/finatra/json/internal/caseclass/validation/validators/FutureTimeValidator.scala b/jackson/src/main/scala/com/twitter/finatra/json/internal/caseclass/validation/validators/FutureTimeValidator.scala index 1f3a8da461..7de655fa34 100644 --- a/jackson/src/main/scala/com/twitter/finatra/json/internal/caseclass/validation/validators/FutureTimeValidator.scala +++ b/jackson/src/main/scala/com/twitter/finatra/json/internal/caseclass/validation/validators/FutureTimeValidator.scala @@ -10,7 +10,7 @@ import com.twitter.finatra.validation.{ } import org.joda.time.DateTime -private[finatra] object FutureTimeValidator { +private[json] object FutureTimeValidator { def errorMessage(resolver: ValidationMessageResolver, value: DateTime) = { @@ -21,7 +21,7 @@ private[finatra] object FutureTimeValidator { /** * Validates if a datetime is in the future. */ -private[finatra] class FutureTimeValidator( +private[json] class FutureTimeValidator( validationMessageResolver: ValidationMessageResolver, annotation: FutureTime ) extends Validator[FutureTime, DateTime](validationMessageResolver, annotation) { diff --git a/jackson/src/main/scala/com/twitter/finatra/json/internal/caseclass/validation/validators/MaxValidator.scala b/jackson/src/main/scala/com/twitter/finatra/json/internal/caseclass/validation/validators/MaxValidator.scala index ec30e74767..7e25320a0c 100644 --- a/jackson/src/main/scala/com/twitter/finatra/json/internal/caseclass/validation/validators/MaxValidator.scala +++ b/jackson/src/main/scala/com/twitter/finatra/json/internal/caseclass/validation/validators/MaxValidator.scala @@ -8,7 +8,7 @@ import com.twitter.finatra.validation.{ Validator } -private[finatra] object MaxValidator { +private[json] object MaxValidator { def errorMessage(resolver: ValidationMessageResolver, value: Any, maxValue: Long): String = { @@ -16,7 +16,7 @@ private[finatra] object MaxValidator { } } -private[finatra] class MaxValidator( +private[json] class MaxValidator( validationMessageResolver: ValidationMessageResolver, annotation: Max ) extends Validator[Max, Any](validationMessageResolver, annotation) { diff --git a/jackson/src/main/scala/com/twitter/finatra/json/internal/caseclass/validation/validators/MinValidator.scala b/jackson/src/main/scala/com/twitter/finatra/json/internal/caseclass/validation/validators/MinValidator.scala index b667bb4b99..dd04407b95 100644 --- a/jackson/src/main/scala/com/twitter/finatra/json/internal/caseclass/validation/validators/MinValidator.scala +++ b/jackson/src/main/scala/com/twitter/finatra/json/internal/caseclass/validation/validators/MinValidator.scala @@ -8,7 +8,7 @@ import com.twitter.finatra.validation.{ Validator } -private[finatra] object MinValidator { +private[json] object MinValidator { def errorMessage(resolver: ValidationMessageResolver, value: Any, minValue: Long): String = { @@ -16,7 +16,7 @@ private[finatra] object MinValidator { } } -private[finatra] class MinValidator( +private[json] class MinValidator( validationMessageResolver: ValidationMessageResolver, annotation: Min ) extends Validator[Min, Any](validationMessageResolver, annotation) { diff --git a/jackson/src/main/scala/com/twitter/finatra/json/internal/caseclass/validation/validators/NotEmptyValidator.scala b/jackson/src/main/scala/com/twitter/finatra/json/internal/caseclass/validation/validators/NotEmptyValidator.scala index 85566e324f..ccc8fb81d5 100644 --- a/jackson/src/main/scala/com/twitter/finatra/json/internal/caseclass/validation/validators/NotEmptyValidator.scala +++ b/jackson/src/main/scala/com/twitter/finatra/json/internal/caseclass/validation/validators/NotEmptyValidator.scala @@ -9,7 +9,7 @@ import com.twitter.finatra.validation.{ Validator } -private[finatra] object NotEmptyValidator { +private[json] object NotEmptyValidator { def errorMessage(resolver: ValidationMessageResolver) = { @@ -17,7 +17,7 @@ private[finatra] object NotEmptyValidator { } } -private[finatra] class NotEmptyValidator( +private[json] class NotEmptyValidator( validationMessageResolver: ValidationMessageResolver, annotation: NotEmpty ) extends Validator[NotEmpty, Any](validationMessageResolver, annotation) { diff --git a/jackson/src/main/scala/com/twitter/finatra/json/internal/caseclass/validation/validators/OneOfValidator.scala b/jackson/src/main/scala/com/twitter/finatra/json/internal/caseclass/validation/validators/OneOfValidator.scala index d714e5b8a4..909a07fb45 100644 --- a/jackson/src/main/scala/com/twitter/finatra/json/internal/caseclass/validation/validators/OneOfValidator.scala +++ b/jackson/src/main/scala/com/twitter/finatra/json/internal/caseclass/validation/validators/OneOfValidator.scala @@ -9,7 +9,7 @@ import com.twitter.finatra.validation.{ Validator } -private[finatra] object OneOfValidator { +private[json] object OneOfValidator { def errorMessage(resolver: ValidationMessageResolver, oneOfValues: Set[String], value: Any) = { @@ -36,7 +36,7 @@ private[finatra] object OneOfValidator { * Validates if one or more values exist in a given set of values. The check for existence is case-sensitive * by default. */ -private[finatra] class OneOfValidator( +private[json] class OneOfValidator( validationMessageResolver: ValidationMessageResolver, annotation: OneOf ) extends Validator[OneOf, Any](validationMessageResolver, annotation) { diff --git a/jackson/src/main/scala/com/twitter/finatra/json/internal/caseclass/validation/validators/PastTimeValidator.scala b/jackson/src/main/scala/com/twitter/finatra/json/internal/caseclass/validation/validators/PastTimeValidator.scala index e65d88d5ea..a1b3a87f6f 100644 --- a/jackson/src/main/scala/com/twitter/finatra/json/internal/caseclass/validation/validators/PastTimeValidator.scala +++ b/jackson/src/main/scala/com/twitter/finatra/json/internal/caseclass/validation/validators/PastTimeValidator.scala @@ -10,7 +10,7 @@ import com.twitter.finatra.validation.{ } import org.joda.time.DateTime -private[finatra] object PastTimeValidator { +private[json] object PastTimeValidator { def errorMessage(resolver: ValidationMessageResolver, value: DateTime) = { @@ -21,7 +21,7 @@ private[finatra] object PastTimeValidator { /** * Validates if a datetime is in the past. */ -private[finatra] class PastTimeValidator( +private[json] class PastTimeValidator( validationMessageResolver: ValidationMessageResolver, annotation: PastTime ) extends Validator[PastTime, DateTime](validationMessageResolver, annotation) { diff --git a/jackson/src/main/scala/com/twitter/finatra/json/internal/caseclass/validation/validators/PatternValidator.scala b/jackson/src/main/scala/com/twitter/finatra/json/internal/caseclass/validation/validators/PatternValidator.scala index 2d245cba1b..7f575dac4f 100644 --- a/jackson/src/main/scala/com/twitter/finatra/json/internal/caseclass/validation/validators/PatternValidator.scala +++ b/jackson/src/main/scala/com/twitter/finatra/json/internal/caseclass/validation/validators/PatternValidator.scala @@ -6,7 +6,7 @@ import com.twitter.finatra.validation._ import com.twitter.util.{Return, Throw, Try} import scala.util.matching.Regex -private[finatra] object PatternValidator { +private[json] object PatternValidator { def errorMessage(resolver: ValidationMessageResolver, value: Any, regex: String): String = { resolver.resolve(classOf[Pattern], value, regex) } @@ -19,7 +19,7 @@ private[finatra] object PatternValidator { * case class ExampleRequest(@Pattern(regexp= "exampleRegex") exampleValue : String) * }}} */ -private[finatra] class PatternValidator( +private[json] class PatternValidator( validationMessageResolver: ValidationMessageResolver, annotation: Pattern) extends Validator[Pattern, Any](validationMessageResolver, annotation) { diff --git a/jackson/src/main/scala/com/twitter/finatra/json/internal/caseclass/validation/validators/RangeValidator.scala b/jackson/src/main/scala/com/twitter/finatra/json/internal/caseclass/validation/validators/RangeValidator.scala index cfd9f17834..aa9769616f 100644 --- a/jackson/src/main/scala/com/twitter/finatra/json/internal/caseclass/validation/validators/RangeValidator.scala +++ b/jackson/src/main/scala/com/twitter/finatra/json/internal/caseclass/validation/validators/RangeValidator.scala @@ -8,7 +8,7 @@ import com.twitter.finatra.validation.{ Validator } -private[finatra] object RangeValidator { +private[json] object RangeValidator { def errorMessage( resolver: ValidationMessageResolver, @@ -21,7 +21,7 @@ private[finatra] object RangeValidator { } } -private[finatra] class RangeValidator( +private[json] class RangeValidator( validationMessageResolver: ValidationMessageResolver, annotation: Range ) extends Validator[Range, Any](validationMessageResolver, annotation) { diff --git a/jackson/src/main/scala/com/twitter/finatra/json/internal/caseclass/validation/validators/SizeValidator.scala b/jackson/src/main/scala/com/twitter/finatra/json/internal/caseclass/validation/validators/SizeValidator.scala index 843ffb228e..d804f9115e 100644 --- a/jackson/src/main/scala/com/twitter/finatra/json/internal/caseclass/validation/validators/SizeValidator.scala +++ b/jackson/src/main/scala/com/twitter/finatra/json/internal/caseclass/validation/validators/SizeValidator.scala @@ -9,7 +9,7 @@ import com.twitter.finatra.validation.{ Validator } -private[finatra] object SizeValidator { +private[json] object SizeValidator { def errorMessage( resolver: ValidationMessageResolver, @@ -35,7 +35,7 @@ private[finatra] object SizeValidator { } } -private[finatra] class SizeValidator( +private[json] class SizeValidator( validationMessageResolver: ValidationMessageResolver, annotation: Size ) extends Validator[Size, Any](validationMessageResolver, annotation) { diff --git a/jackson/src/main/scala/com/twitter/finatra/json/internal/caseclass/validation/validators/TimeGranularityValidator.scala b/jackson/src/main/scala/com/twitter/finatra/json/internal/caseclass/validation/validators/TimeGranularityValidator.scala index 6170a41ebf..80ca5b3a85 100644 --- a/jackson/src/main/scala/com/twitter/finatra/json/internal/caseclass/validation/validators/TimeGranularityValidator.scala +++ b/jackson/src/main/scala/com/twitter/finatra/json/internal/caseclass/validation/validators/TimeGranularityValidator.scala @@ -12,7 +12,7 @@ import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit._ import org.joda.time.{DateTime, DateTimeZone} -private[finatra] object TimeGranularityValidator { +private[json] object TimeGranularityValidator { def errorMessage( resolver: ValidationMessageResolver, @@ -32,7 +32,7 @@ private[finatra] object TimeGranularityValidator { /** * Validates if a given value is of a given time granularity (e.g., days, hours, seconds) */ -private[finatra] class TimeGranularityValidator( +private[json] class TimeGranularityValidator( validationMessageResolver: ValidationMessageResolver, annotation: TimeGranularity ) extends Validator[TimeGranularity, DateTime](validationMessageResolver, annotation) { diff --git a/jackson/src/main/scala/com/twitter/finatra/json/internal/caseclass/validation/validators/UUIDValidator.scala b/jackson/src/main/scala/com/twitter/finatra/json/internal/caseclass/validation/validators/UUIDValidator.scala index 71836b0aeb..6e7c0a97dc 100644 --- a/jackson/src/main/scala/com/twitter/finatra/json/internal/caseclass/validation/validators/UUIDValidator.scala +++ b/jackson/src/main/scala/com/twitter/finatra/json/internal/caseclass/validation/validators/UUIDValidator.scala @@ -11,7 +11,7 @@ import com.twitter.finatra.validation.{ import com.twitter.util.Try import java.util.{UUID => JUUID} -private[finatra] object UUIDValidator { +private[json] object UUIDValidator { def errorMessage(resolver: ValidationMessageResolver, value: String) = { @@ -23,7 +23,7 @@ private[finatra] object UUIDValidator { } } -private[finatra] class UUIDValidator( +private[json] class UUIDValidator( validationMessageResolver: ValidationMessageResolver, annotation: UUID ) extends Validator[UUID, String](validationMessageResolver, annotation) { diff --git a/jackson/src/main/scala/com/twitter/finatra/json/modules/NullValidationCaseClassModule.scala b/jackson/src/main/scala/com/twitter/finatra/json/modules/NullValidationCaseClassModule.scala new file mode 100644 index 0000000000..d13ef56e12 --- /dev/null +++ b/jackson/src/main/scala/com/twitter/finatra/json/modules/NullValidationCaseClassModule.scala @@ -0,0 +1,14 @@ +package com.twitter.finatra.json.modules + +import com.fasterxml.jackson.module.scala._ +import com.twitter.finatra.json.internal.caseclass.jackson.CaseClassDeserializers +import com.twitter.finatra.json.internal.caseclass.validation.NullValidationProvider + +/** + * Module that supports skipping validation of Finatra validation annotations. + */ +private[json] object NullValidationCaseClassModule extends JacksonModule { + override def getModuleName = "NullValidationCaseClassModule" + + this += { _.addDeserializers(new CaseClassDeserializers(NullValidationProvider)) } +} diff --git a/jackson/src/main/scala/com/twitter/finatra/json/modules/NullValidationFinatraJacksonModule.scala b/jackson/src/main/scala/com/twitter/finatra/json/modules/NullValidationFinatraJacksonModule.scala new file mode 100644 index 0000000000..9cd35d1926 --- /dev/null +++ b/jackson/src/main/scala/com/twitter/finatra/json/modules/NullValidationFinatraJacksonModule.scala @@ -0,0 +1,13 @@ +package com.twitter.finatra.json.modules + +/** + * Provides a FinatraJacksonModule that will treat all fields as acceptable during + * Finatra Validation of annotations. + */ +object NullValidationFinatraJacksonModule extends NullValidationFinatraJacksonModule + +class NullValidationFinatraJacksonModule extends FinatraJacksonModule { + + override val finatraCaseClassModule = Some(NullValidationCaseClassModule) + +} diff --git a/jackson/src/test/scala/com/twitter/finatra/json/tests/OptionalValidationTest.scala b/jackson/src/test/scala/com/twitter/finatra/json/tests/OptionalValidationTest.scala new file mode 100644 index 0000000000..8b20d05431 --- /dev/null +++ b/jackson/src/test/scala/com/twitter/finatra/json/tests/OptionalValidationTest.scala @@ -0,0 +1,85 @@ +package com.twitter.finatra.json.tests + +import com.twitter.finatra.json.FinatraObjectMapper +import com.twitter.finatra.json.internal.caseclass.exceptions.CaseClassMappingException +import com.twitter.finatra.json.modules.{FinatraJacksonModule, NullValidationFinatraJacksonModule} +import com.twitter.finatra.validation.{MethodValidation, Min, NotEmpty, OneOf, ValidationResult} +import com.twitter.inject.Test +import com.twitter.inject.domain.WrappedValue + +private object OptionalValidationTest { + case class State( + @OneOf(Array("active", "inactive")) + state: String) + extends WrappedValue[String] + + case class Threshold( + @NotEmpty id: Option[String], + @Min(0) lowerBound: Int, + @Min(0) upperBound: Int, + state: State) { + @MethodValidation + def method = ValidationResult.validate( + lowerBound <= upperBound, + "Lower Bound cannot be greater than Upper Bound" + ) + } +} + +class OptionalValidationTest extends Test { + import OptionalValidationTest._ + + private val defaultMapper = { + val module = FinatraJacksonModule + new FinatraObjectMapper(module.provideScalaObjectMapper(null)) + } + + private val nullValidationMapper = { + val module = NullValidationFinatraJacksonModule + new FinatraObjectMapper(module.provideScalaObjectMapper(null)) + } + + test("default mapper will trigger NotEmpty validation") { + val invalid = Threshold(Some(""), 1, 4, State("active")) + intercept[CaseClassMappingException] { + check(defaultMapper, invalid) + } + } + + test("default mapper will trigger lower and upper min validation") { + val invalid = Threshold(Some(""), -1, -1, State("active")) + intercept[CaseClassMappingException] { + check(defaultMapper, invalid) + } + } + + test("default mapper will trigger invalid state") { + val invalid = Threshold(Some("id"), 4, 6, State("other")) + intercept[CaseClassMappingException] { + check(defaultMapper, invalid) + } + } + + test("default mapper will trigger method validation") { + val invalid = Threshold(None, 8, 4, State("active")) + intercept[CaseClassMappingException] { + check(defaultMapper, invalid) + } + } + + test("no-op mapper will allow values that are considered invalid") { + val invalid = Threshold(Some(""), -1, -3, State("other")) + check(nullValidationMapper, invalid) + } + + test("no-op mapper will never call method validation") { + val invalid = Threshold(None, 8, 4, State("active")) + check(nullValidationMapper, invalid) + } + + def check[T: Manifest](mapper: FinatraObjectMapper, check: T): Unit = { + val str = mapper.writeValueAsString(check) + val result = mapper.parse[T](str) + check should equal(result) + } +}