From 7569159094e516d0fc9e0c246dceb887b243e870 Mon Sep 17 00:00:00 2001 From: Kevin Cianfarini Date: Sat, 3 Aug 2019 13:50:49 -0400 Subject: [PATCH] Add value validation using lambdas --- .../java/hu/autsoft/krate/DefaultTests.kt | 32 +++++ .../java/hu/autsoft/krate/OptionalTests.kt | 14 +++ .../java/hu/autsoft/krate/TestKrate.kt | 6 + .../main/kotlin/hu/autsoft/krate/Functions.kt | 6 +- .../hu/autsoft/krate/ValidatedFunctions.kt | 116 ++++++++++++++++++ .../validated/ValidatedPreferenceDelegate.kt | 27 ++++ 6 files changed, 200 insertions(+), 1 deletion(-) create mode 100644 krate/src/androidTest/java/hu/autsoft/krate/DefaultTests.kt create mode 100644 krate/src/main/kotlin/hu/autsoft/krate/ValidatedFunctions.kt create mode 100644 krate/src/main/kotlin/hu/autsoft/krate/validated/ValidatedPreferenceDelegate.kt diff --git a/krate/src/androidTest/java/hu/autsoft/krate/DefaultTests.kt b/krate/src/androidTest/java/hu/autsoft/krate/DefaultTests.kt new file mode 100644 index 0000000..edb28b4 --- /dev/null +++ b/krate/src/androidTest/java/hu/autsoft/krate/DefaultTests.kt @@ -0,0 +1,32 @@ +package hu.autsoft.krate + +import android.support.test.InstrumentationRegistry +import android.support.test.runner.AndroidJUnit4 +import junit.framework.Assert.assertEquals +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class DefaultTests { + + private lateinit var testKrate: TestKrate + + @Before + fun setup() { + val appContext = InstrumentationRegistry.getTargetContext() + testKrate = TestKrate(appContext) + } + + @Test + fun testValidatedDefaultFloatDelegate() { + testKrate.defaultValidatedFloat = .67f + + assertEquals(.67f, testKrate.defaultValidatedFloat) + } + + @Test(expected = IllegalArgumentException::class) + fun testValidatedDefaultFloatDelegateThrowsException() { + testKrate.defaultValidatedFloat = 5.0f + } +} \ No newline at end of file diff --git a/krate/src/androidTest/java/hu/autsoft/krate/OptionalTests.kt b/krate/src/androidTest/java/hu/autsoft/krate/OptionalTests.kt index b3cb214..63d5ee5 100644 --- a/krate/src/androidTest/java/hu/autsoft/krate/OptionalTests.kt +++ b/krate/src/androidTest/java/hu/autsoft/krate/OptionalTests.kt @@ -103,4 +103,18 @@ class OptionalTests { assertEquals(null, testKrate.optionalStringSet) } + @Test(expected = IllegalArgumentException::class) + fun testOptionalStringValidatedPrefFailsValidation() { + assertEquals(null, testKrate.optionalValidatedString) + + testKrate.optionalValidatedString = "Lorem ipsum dolor sit amet" + } + + @Test + fun testOptionalStringValidatedPassesValidation() { + assertEquals(null, testKrate.optionalValidatedString) + + testKrate.optionalValidatedString = "Lorem" + } + } diff --git a/krate/src/androidTest/java/hu/autsoft/krate/TestKrate.kt b/krate/src/androidTest/java/hu/autsoft/krate/TestKrate.kt index 5b8d92b..2fc70dc 100644 --- a/krate/src/androidTest/java/hu/autsoft/krate/TestKrate.kt +++ b/krate/src/androidTest/java/hu/autsoft/krate/TestKrate.kt @@ -14,5 +14,11 @@ class TestKrate(context: Context) : SimpleKrate(context) { var optionalLong by longPref("optionalLong") var optionalString by stringPref("optionalString") var optionalStringSet by stringSetPref("optionalStringSet") + var optionalValidatedString by stringPref("validatedString") { + it?.length ?: 5 == 5 + } + var defaultValidatedFloat by floatPref("defaultFloat", 0.0f) { + it > 0.0f && it < 1.0f + } } diff --git a/krate/src/main/kotlin/hu/autsoft/krate/Functions.kt b/krate/src/main/kotlin/hu/autsoft/krate/Functions.kt index 3d3d342..9a3af17 100644 --- a/krate/src/main/kotlin/hu/autsoft/krate/Functions.kt +++ b/krate/src/main/kotlin/hu/autsoft/krate/Functions.kt @@ -46,6 +46,10 @@ public fun Krate.longPref(key: String): ReadWriteProperty { /** * Creates an optional preference of type String with the given [key] in this [Krate] instance. + * + * @param [key] the key to identify this value in SharedPreferences + * + * @return [StringDelegate] */ public fun Krate.stringPref(key: String): ReadWriteProperty { return StringDelegate(key) @@ -99,4 +103,4 @@ public fun Krate.stringPref(key: String, defaultValue: String): ReadWritePropert */ public fun Krate.stringSetPref(key: String, defaultValue: Set): ReadWriteProperty> { return StringSetDelegateWithDefault(key, defaultValue) -} +} \ No newline at end of file diff --git a/krate/src/main/kotlin/hu/autsoft/krate/ValidatedFunctions.kt b/krate/src/main/kotlin/hu/autsoft/krate/ValidatedFunctions.kt new file mode 100644 index 0000000..962e30e --- /dev/null +++ b/krate/src/main/kotlin/hu/autsoft/krate/ValidatedFunctions.kt @@ -0,0 +1,116 @@ +@file:Suppress("RedundantVisibilityModifier") + +package hu.autsoft.krate + +import hu.autsoft.krate.validated.ValidatedPreferenceDelegate +import hu.autsoft.krate.default.FloatDelegateWithDefault +import hu.autsoft.krate.default.IntDelegateWithDefault +import hu.autsoft.krate.default.LongDelegateWithDefault +import hu.autsoft.krate.default.StringDelegateWithDefault +import hu.autsoft.krate.default.StringSetDelegateWithDefault +import hu.autsoft.krate.optional.FloatDelegate +import hu.autsoft.krate.optional.IntDelegate +import hu.autsoft.krate.optional.LongDelegate +import hu.autsoft.krate.optional.StringDelegate +import hu.autsoft.krate.optional.StringSetDelegate +import kotlin.properties.ReadWriteProperty + +/** + * Creates a validated, optional preference of type [Float] with the given [key]. + * + * If a value being set to this preference returns `false` when checked by [isValid], + * an [IllegalArgumentException] will be thrown. + */ +public fun Krate.floatPref(key: String, isValid: (Float?) -> Boolean): ReadWriteProperty { + return ValidatedPreferenceDelegate(FloatDelegate(key), isValid) +} + +/** + * Creates a validated, non-optional preference of type [Float] with the given [key]. + * + * If a value being set to this preference returns `false` when checked by [isValid], + * an [IllegalArgumentException] will be thrown. + */ +public fun Krate.floatPref(key: String, default: Float, isValid: (Float) -> Boolean): ReadWriteProperty { + return ValidatedPreferenceDelegate(FloatDelegateWithDefault(key, default), isValid) +} + +/** + * Creates a validated, optional preference of type [Int] with the given [key]. + * + * If a value being set to this preference returns `false` when checked by [isValid], + * an [IllegalArgumentException] will be thrown. + */ +public fun Krate.intPref(key: String, isValid: (Int?) -> Boolean): ReadWriteProperty { + return ValidatedPreferenceDelegate(IntDelegate(key), isValid) +} + +/** + * Creates a validated, non-optional preference of type [Int] with the given [key]. + * + * If a value being set to this preference returns `false` when checked by [isValid], + * an [IllegalArgumentException] will be thrown. + */ +public fun Krate.intPref(key: String, default: Int, isValid: (Int) -> Boolean): ReadWriteProperty { + return ValidatedPreferenceDelegate(IntDelegateWithDefault(key, default), isValid) +} + +/** + * Creates a validated, optional preference of type [Long] with the given [key]. + * + * If a value being set to this preference returns `false` when checked by [isValid], + * an [IllegalArgumentException] will be thrown. + */ +public fun Krate.longPref(key: String, isValid: (Long?) -> Boolean): ReadWriteProperty { + return ValidatedPreferenceDelegate(LongDelegate(key), isValid) +} + +/** + * Creates a validated, non-optional preference of type [Long] with the given [key]. + * + * If a value being set to this preference returns `false` when checked by [isValid], + * an [IllegalArgumentException] will be thrown. + */ +public fun Krate.longPref(key: String, defaultValue: Long, isValid: (Long) -> Boolean): ReadWriteProperty { + return ValidatedPreferenceDelegate(LongDelegateWithDefault(key, defaultValue), isValid) +} + +/** + * Creates a validated, optional preference of type [String] with the given [key]. + * + * If a value being set to this preference returns `false` when checked by [isValid], + * an [IllegalArgumentException] will be thrown. + */ +public fun Krate.stringPref(key: String, isValid: (String?) -> Boolean): ReadWriteProperty { + return ValidatedPreferenceDelegate(StringDelegate(key), isValid) +} + +/** + * Creates a validated, non-optional preference of type [String] with the given [key]. + * + * If a value being set to this preference returns `false` when checked by [isValid], + * an [IllegalArgumentException] will be thrown. + */ +public fun Krate.stringPref(key: String, default: String, isValid: (String) -> Boolean): ReadWriteProperty { + return ValidatedPreferenceDelegate(StringDelegateWithDefault(key, default), isValid) +} + +/** + * Creates a validated, optional preference of type [Set] with the given [key]. + * + * If a value being set to this preference returns `false` when checked by [isValid], + * an [IllegalArgumentException] will be thrown. + */ +public fun Krate.stringSetPref(key: String, isValid: (Set?) -> Boolean): ReadWriteProperty?> { + return ValidatedPreferenceDelegate(StringSetDelegate(key), isValid) +} + +/** + * Creates a validated, non-optional preference of type [Set] with the given [key]. + * + * If a value being set to this preference returns `false` when checked by [isValid], + * an [IllegalArgumentException] will be thrown. + */ +public fun Krate.stringSetPref(key: String, defaultValue: Set, isValid: (Set) -> Boolean): ReadWriteProperty> { + return ValidatedPreferenceDelegate(StringSetDelegateWithDefault(key, defaultValue), isValid) +} \ No newline at end of file diff --git a/krate/src/main/kotlin/hu/autsoft/krate/validated/ValidatedPreferenceDelegate.kt b/krate/src/main/kotlin/hu/autsoft/krate/validated/ValidatedPreferenceDelegate.kt new file mode 100644 index 0000000..84c506c --- /dev/null +++ b/krate/src/main/kotlin/hu/autsoft/krate/validated/ValidatedPreferenceDelegate.kt @@ -0,0 +1,27 @@ +package hu.autsoft.krate.validated + +import hu.autsoft.krate.Krate +import kotlin.properties.ReadWriteProperty +import kotlin.reflect.KProperty + +/** + * [ValidatedPreferenceDelegate] is a generic [ReadWriteProperty] that can be used to + * validate values that are being set. + * + * @param [delegate] the [ReadWriteProperty] implementation that is used for delegation + * @param [isValid] the lambda used to validate property values on [setValue] + */ +internal class ValidatedPreferenceDelegate( + private val delegate: ReadWriteProperty, + private val isValid: (T) -> Boolean +) : ReadWriteProperty by delegate { + + override operator fun setValue(thisRef: Krate, property: KProperty<*>, value: T) { + if (!isValid(value)) { + throw IllegalArgumentException("$value is not valid for ${property.name}.") + } + + delegate.setValue(thisRef, property, value) + } + +} \ No newline at end of file