diff --git a/README.md b/README.md index d81ab48..07663da 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -![Java CI with Maven](https://github.com/jitsi/jitsi-metaconfig/workflows/Java%20CI%20with%20Maven/badge.svg) +![Java CI with Maven](https://github.com/jitsi/jitsi-metaconfig/workflows/Java%20CI%20with%20Maven/badge.svg) ![Code Coverage](https://codecov.io/gh/jitsi/jitsi-metaconfig/branch/master/graph/badge.svg) # jitsi-metaconfig @@ -15,7 +15,7 @@ supports marking old properties as deprecated to ease the transition to removing ### Example config properties: ```kotlin class Foo { - // Simple property + // A simple property with just a key and a source val enabled: Boolean by config("app.enabled".from(myConfigSource)) // Optional property @@ -23,30 +23,36 @@ class Foo { // Convert the type (retrieve as a Long, convert to a Duration) val interval: Duration by config { - retrieve("app.interval-ms".from(myConfigSource).asType().andConvertBy(Duration::ofMillis)) + "app.interval-ms".from(myConfigSource).convertFrom(Duration::ofMillis) } // Transform the value (invert the retrieved boolean value) val enabled: Boolean by config { - retrieve("app.disabled".from(myConfigSource).andTransformBy { !it }) + "app.disabled".from(myConfigSource).andTransformBy { !it } } // Search for value in a legacy config file and then the new one val enabled: Boolean by config { - retrieve("old.path.app.enabled".from(legacyConfigSource)) - retrieve("new.path.app.enabled".from(newConfigSource)) + "old.path.app.enabled".from(legacyConfigSource) + "new.path.app.enabled".from(newConfigSource) } // Search for value in a legacy config file and then the new one, mark the old one as deprecated val enabled: Boolean by config { - retrieve("old.path.app.enabled".from(legacyConfigSource).softDeprecated("use 'new.path.app.enabled' in new config source") - retrieve("new.path.app.enabled".from(newConfigSource)) + "old.path.app.enabled".from(legacyConfigSource).softDeprecated("use 'new.path.app.enabled' in new config source") + "new.path.app.enabled".from(newConfigSource) + } + + // Use the result of a lambda as a value + val port: Int by config { + "path.to.port".from(newConfigSource) + "default" { 8080 } } // Only allow access to port if 'enabled' is true (throws an exception otherwise) val port: Int by config { onlyIf("Server is enabled", ::enabled) { - retrieve("path.to.port".from(newConfigSource)) + "path.to.port".from(newConfigSource) } } } diff --git a/docs/Debugging.md b/docs/Debugging.md index b5266df..6772900 100644 --- a/docs/Debugging.md +++ b/docs/Debugging.md @@ -8,12 +8,12 @@ For example, to set up the jitsi-metaconfig logger: val logger = Logger.getLogger("metaconfig") val metaconfigLogger = object : MetaconfigLogger { - override fun error(block: () -> String) { + override fun error(block: () -> String) { logger.error(block()) - } - override fun warn(block: () -> String) { + } + override fun warn(block: () -> String) { logger.warn(block()) - } + } override fun debug(block: () -> String) { logger.fine(block) } @@ -23,10 +23,10 @@ val metaconfigLogger = object : MetaconfigLogger { ### Logging when searching for a value If you have a property: ```kotlin -val num: Int by config { - retrieve("some.missing.path".from(legacyConfig)) - retrieve("new.num".from(newConfig)) - retrieve("default value") { 8080 } +val num: Int by config { + "some.missing.path".from(legacyConfig) + "new.num".from(newConfig) + "default value" { 8080 } } ``` diff --git a/docs/DelegateHelpers.md b/docs/DelegateHelpers.md index 52d19ae..b54a179 100644 --- a/docs/DelegateHelpers.md +++ b/docs/DelegateHelpers.md @@ -11,8 +11,8 @@ val myProperty: Int by config("path.to.property".from(myConfigSource)) To define a property which checks multiple configuration sources, stopping at the first value it finds, use: ```kotlin val myProperty: Int by config { - retrieve("legacy.path".from(legacyConfigSource)) - retrieve("new.path".from(newConfigSource)) + "legacy.path".from(legacyConfigSource) + "new.path".from(newConfigSource) } ``` This will first try to retrieve an `Int` at `legacy.path` from `legacyConfigSource`, if it isn't found, it will try to retrieve an `Int` at `new.path` from `newConfigSource`. @@ -22,7 +22,7 @@ This will first try to retrieve an `Int` at `legacy.path` from `legacyConfigSour To transform the retrieved value in some way (here, by inverting the retrieved boolean), use: ```kotlin val myProperty: Boolean by config { - retrieve("path.to.property".from(myConfigSource).andTransformBy { !it }) + "path.to.property".from(myConfigSource).andTransformBy { !it } } ``` This is useful if the semantics of a property were changed, for example: @@ -46,15 +46,15 @@ This is useful if the semantics of a property were changed, for example: The property would be: ```kotlin val serverEnabled: Boolean by config { - retrieve("app.server.enabled".from(oldConfig)) + "app.server.enabled".from(oldConfig) // Invert the value to match if it's 'enabled' - retrieve("app.server.disabled".from(newConfig).andTransformBy { !it }) + "app.server.disabled".from(newConfig).andTransformBy { !it } } ``` Converting the type of a value is also possible. This is useful if you want the code to use a friendlier type than the config (say a `Duration` instead of a `Long` representing milliseconds): ```kotlin val healthInterval: Duration by config { - retrieve("app.health.interval".from(legacyConfigSource).asType().andConvertBy(Duration::ofMillis) + "app.health.interval".from(legacyConfigSource).convertFrom(Duration::ofMillis) } ``` --- @@ -62,18 +62,18 @@ val healthInterval: Duration by config { It's possible to pull a value from anywhere (e.g. a property of an object, or even just a hard-coded default) by passing a lambda: ```kotlin val port: Int by config { - retrieve("path.to.port".from(myConfig)) + "path.to.port".from(myConfig) // Since the lambda is opaque, the description gives some context - retrieve("Foo::port") { foo.port } + "Foo::port" { foo.port } } ``` This will first try to retrieve an `Int` at `path.to.port` from `myConfig` and, if it can't be found, will grab the `port` member of `foo`. This could also be used to set a default value: ```kotlin val port: Int by config { - retrieve("path.to.port".from(myConfig)) + "path.to.port".from(myConfig) // Since the lambda is opaque, the description gives some context - retrieve("default") { 8080 } + "default" { 8080 } } ``` --- @@ -87,9 +87,9 @@ val serverEnabled: Boolean by config("app.server.enabled".from(config)) val port: Int by config { onlyIf("Server is enabled", ::serverEnabled) { - retrieve("path.to.port".from(myConfig)) + "path.to.port".from(myConfig) // Since the lambda is opaque, the description gives some context - retrieve("default") { 8080 } + "default" { 8080 } } } ``` @@ -103,8 +103,8 @@ val myProperty: Int? by optionalconfig("path.to.property".from(myConfigSource)) Or ```kotlin val myProperty: Int? by optionalconfig { - retrieve("path.to.property".from(myConfigSource)) - retrieve("new.path.to.property".from(myConfigSource)) + "path.to.property".from(myConfigSource) + "new.path.to.property".from(myConfigSource) } ``` @@ -136,15 +136,15 @@ And you want to move the property: You'd define a property in the code to look in both places, so deployments with the old configuration don't break: ```kotlin val serverEnabled: Boolean by config { - retrieve("app.server.enabled".from(myConfig)) - retrieve("app.api.server.enabled".from(myConfig)) + "app.server.enabled".from(myConfig) + "app.api.server.enabled".from(myConfig) } ``` But you want users to know that `app.server.enabled` is deprecated and they should use the new name/path. You can mark the old path as deprecated: ```kotlin val serverEnabled: Boolean by config { - retrieve("app.server.enabled".from(myConfig).softDeprecated("use 'app.api.server.enabled'") - retrieve("app.api.server.enabled".from(myConfig)) + "app.server.enabled".from(myConfig).softDeprecated("use 'app.api.server.enabled'") + "app.api.server.enabled".from(myConfig) } ``` If a value is retrieved via "app.server.enabled" from myConfig, a warning will be logged: @@ -156,8 +156,8 @@ This warning is only printed once, and only if the value marked as deprecated wa Values can also be _hard_ deprecated: ```kotlin val serverEnabled: Boolean by config { - retrieve("app.server.enabled".from(myConfig).hardDeprecated("use 'app.api.server.enabled'") - retrieve("app.api.server.enabled".from(myConfig)) + "app.server.enabled".from(myConfig).hardDeprecated("use 'app.api.server.enabled'") + "app.api.server.enabled".from(myConfig) } ``` And `ConfigException.UnableToRetrieve.Deprecated` will be thrown if that value is used as the result. diff --git a/src/main/kotlin/org/jitsi/metaconfig/ConfigPropertyState.kt b/src/main/kotlin/org/jitsi/metaconfig/ConfigPropertyState.kt deleted file mode 100644 index 3166152..0000000 --- a/src/main/kotlin/org/jitsi/metaconfig/ConfigPropertyState.kt +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Copyright @ 2018 - present 8x8, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.jitsi.metaconfig - -import org.jitsi.metaconfig.supplier.ConfigSourceSupplier -import org.jitsi.metaconfig.supplier.ConfigValueSupplier -import org.jitsi.metaconfig.supplier.TypeConvertingSupplier -import org.jitsi.metaconfig.supplier.ValueTransformingSupplier -import kotlin.reflect.KType -import kotlin.reflect.typeOf - -/** - * State classes to aid in the construction of a [ConfigValueSupplier]. - * - * These classes allow constructing the values needed to build a [ConfigValueSupplier] piece-by-piece, and only - * allow the creation of a [ConfigValueSupplier] when all the required pieces are present. Optional operations - * can be added as well (for doing value or type conversions). - * - * Note that currently the construction must be performed in a specific - * order: (key, source, type [,valueTransformation|typeTransformation]). And only one transformation - * (value or type) is allowed. All of this could be changed by adding the more methods to the different states. - */ -sealed class ConfigPropertyState { - sealed class Complete : ConfigPropertyState() { - /** - * Build a [ConfigValueSupplier]. Required for any subclass of [Complete]. - */ - abstract fun build(): ConfigValueSupplier - fun softDeprecated(msg: String) = withDeprecation(softDeprecation(msg)) - fun hardDeprecated(msg: String) = withDeprecation(hardDeprecation(msg)) - protected abstract fun withDeprecation(deprecation: Deprecation): Complete - - /** - * A simple lookup of a property value from the given source as the given type - */ - class NoTransformation( - val key: String, - val source: ConfigSource, - val type: KType, - val deprecation: Deprecation - ) : Complete() { - /** - * Add a value transformation operation - */ - fun andTransformBy(transformer: (T) -> T): ValueTransformation { - return ValueTransformation(ConfigSourceSupplier(key, source, type, deprecation), transformer) - } - - /** - * Add a type conversion operation - */ - fun andConvertBy(converter: (T) -> NewType): TypeConversion { - return TypeConversion(ConfigSourceSupplier(key, source, type, deprecation), converter) - } - - /** - * We allow updating the type here so that helper functions can fill in the type - * automatically (since they can derive it from the call) so the caller doesn't have - * to, however, the helpers will use the final type as a guess; if the caller wants - * to retrieve the property as another type and then convert it, we need to let them - * set the retrieved type via this asType call and then use [andConvertBy]. For example: - * val bool: Boolean by config { - * // No need to use 'asType', we can determine that - * "app.enabled".from(newConfigSource) - * // Here the caller didn't want to retrieve it as bool, so they override the type - * // via another call to 'asType' and then convert the value to a Boolean. - * "app.enabled".from(newConfigSource).asType.andConvertBy { it > 0 } - * } - */ - inline fun asType(): NoTransformation { - return NoTransformation(key, source, typeOf(), noDeprecation()) - } - - override fun build(): ConfigValueSupplier { - return ConfigSourceSupplier(key, source, type, deprecation) - } - - override fun withDeprecation(deprecation: Deprecation) = - NoTransformation(key, source, type, deprecation) - } - - /** - * A lookup which transforms the value in some way - */ - class ValueTransformation( - val innerSupplier: ConfigValueSupplier, - val transformer: (T) -> T - ) : Complete() { - override fun build(): ConfigValueSupplier { - return ValueTransformingSupplier(innerSupplier, transformer) - } - - override fun withDeprecation(deprecation: Deprecation) = - ValueTransformation(innerSupplier.withDeprecation(deprecation), transformer) - } - - /** - * A lookup which converts the type the value was retrieved as to another type - */ - class TypeConversion( - val innerSupplier: ConfigValueSupplier, - val converter: (OriginalType) -> NewType - ) : Complete() { - override fun build(): ConfigValueSupplier { - return TypeConvertingSupplier(innerSupplier, converter) - } - - override fun withDeprecation(deprecation: Deprecation) = - TypeConversion(innerSupplier.withDeprecation(deprecation), converter) - } - } - - sealed class Incomplete : ConfigPropertyState() { - /** - * The initial empty state - */ - object Empty : Incomplete() { - fun lookup(key: String): KeyOnly = KeyOnly(key) - } - - /** - * Only the config key is present - */ - class KeyOnly(val key: String) : Incomplete() { - fun from(configSource: ConfigSource): KeyAndSource { - return KeyAndSource(key, configSource, noDeprecation()) - } - } - - /** - * The config key and source are present - */ - class KeyAndSource(val key: String, val source: ConfigSource, val deprecation: Deprecation) : Incomplete() { - inline fun asType(): Complete.NoTransformation { - return Complete.NoTransformation(key, source, typeOf(), deprecation) - } - fun asType(type: KType): Complete.NoTransformation { - return Complete.NoTransformation(key, source, type, deprecation) - } - - // These don't make sense until we have at least a key and a source, so put them in - // KeyAndSource instead of defining at Incomplete (or at ConfigPropertyState) - fun softDeprecated(msg: String) = withDeprecation(softDeprecation(msg)) - fun hardDeprecated(msg: String) = withDeprecation(hardDeprecation(msg)) - protected fun withDeprecation(deprecation: Deprecation) = - KeyAndSource(key, source, deprecation) - } - } -} diff --git a/src/main/kotlin/org/jitsi/metaconfig/Delegates.kt b/src/main/kotlin/org/jitsi/metaconfig/Delegates.kt index 259a8db..74d8f0f 100644 --- a/src/main/kotlin/org/jitsi/metaconfig/Delegates.kt +++ b/src/main/kotlin/org/jitsi/metaconfig/Delegates.kt @@ -16,6 +16,7 @@ package org.jitsi.metaconfig +import org.jitsi.metaconfig.supplier.ConfigSourceSupplier import org.jitsi.metaconfig.supplier.ConfigValueSupplier import org.jitsi.metaconfig.supplier.FallbackSupplier import kotlin.reflect.KProperty @@ -55,9 +56,7 @@ class OptionalConfigDelegate(private val supplier: ConfigValueSupplier< * * throws [ConfigException.UnableToRetrieve] if the property couldn't be retrieved */ -inline fun config(configPropertyState: ConfigPropertyState.Incomplete.KeyAndSource): ConfigDelegate { - return ConfigDelegate(configPropertyState.asType().build()) -} +inline fun config(supplier: ConfigSourceSupplier): ConfigDelegate = ConfigDelegate(supplier) /** * Create a [ConfigDelegate] which will query multiple [ConfigValueSupplier]s, in order, for a @@ -66,12 +65,12 @@ inline fun config(configPropertyState: ConfigPropertyState.Inc * throws [ConfigException.UnableToRetrieve] if the property couldn't be retrieved */ inline fun config(block: SupplierBuilder.() -> Unit): ConfigDelegate { - val supplier = SupplierBuilder(typeOf()).apply(block) - return if (supplier.suppliers.size == 1) { + val supplierBuilder = SupplierBuilder(typeOf()).apply(block) + return if (supplierBuilder.suppliers.size == 1) { // Avoid wrapping in a FallbackSupplier if we don't need one - ConfigDelegate(supplier.suppliers.first()) + ConfigDelegate(supplierBuilder.suppliers.first()) } else { - return ConfigDelegate(FallbackSupplier(supplier.suppliers)) + ConfigDelegate(FallbackSupplier(supplierBuilder.suppliers)) } } @@ -93,20 +92,19 @@ inline fun configSupplier(block: SupplierBuilder.() -> Unit * Create an [OptionalConfigDelegate] for a single property (no fallback) from only a key and source (filling in * the type automatically), returns null if the property couldn't be retrieved */ -inline fun optionalconfig(configPropertyState: ConfigPropertyState.Incomplete.KeyAndSource): OptionalConfigDelegate { - return OptionalConfigDelegate(configPropertyState.asType().build()) -} +inline fun optionalconfig(supplier: ConfigSourceSupplier): OptionalConfigDelegate = + OptionalConfigDelegate(supplier) /** * Create an [OptionalConfigDelegate] which queries multiple [ConfigValueSupplier]s for a property, returning * null if the property couldn't be retrieved */ inline fun optionalconfig(block: SupplierBuilder.() -> Unit): OptionalConfigDelegate { - val supplier = SupplierBuilder(typeOf()).apply(block) - return if (supplier.suppliers.size == 1) { + val supplierBuilder = SupplierBuilder(typeOf()).apply(block) + return if (supplierBuilder.suppliers.size == 1) { // Avoid wrapping in a FallbackSupplier if we don't need one - OptionalConfigDelegate(supplier.suppliers.first()) + OptionalConfigDelegate(supplierBuilder.suppliers.first()) } else { - return OptionalConfigDelegate(FallbackSupplier(supplier.suppliers)) + OptionalConfigDelegate(FallbackSupplier(supplierBuilder.suppliers)) } } diff --git a/src/main/kotlin/org/jitsi/metaconfig/SupplierBuilder.kt b/src/main/kotlin/org/jitsi/metaconfig/SupplierBuilder.kt index 80eb7c8..12fa47b 100644 --- a/src/main/kotlin/org/jitsi/metaconfig/SupplierBuilder.kt +++ b/src/main/kotlin/org/jitsi/metaconfig/SupplierBuilder.kt @@ -19,55 +19,109 @@ package org.jitsi.metaconfig import org.jitsi.metaconfig.supplier.ConditionalSupplier +import org.jitsi.metaconfig.supplier.ConfigSourceSupplier import org.jitsi.metaconfig.supplier.ConfigValueSupplier import org.jitsi.metaconfig.supplier.LambdaSupplier +import org.jitsi.metaconfig.supplier.TypeConvertingSupplier +import org.jitsi.metaconfig.supplier.ValueTransformingSupplier import kotlin.reflect.KType +import kotlin.reflect.typeOf /** * This class enables us to implicitly inject the type when a caller is building a property, for example: * val bool: Boolean by config { * "some.path".from(configSource) // no need to explicitly set the type - * "some.other.path".from(configSource).andTransformBy { !it } // again, don't need to set the type + * "some.other.path".from(configSource).transformedBy { !it } // again, don't need to set the type * // can override the inferred type when you want to convert - * "some.third.path".from(configSource).asType().andConvertBy { it > 0 } + * "some.third.path".from(configSource).convertFrom { it > 0 } * } */ class SupplierBuilder(val finalType: KType) { val suppliers = mutableListOf>() - fun retrieve(sbs: ConfigPropertyState.Complete) { - suppliers += sbs.build() + /** + * Given a key and a source, create a [ConfigSourceSupplier] with an inferred type. + */ + fun String.from(configSource: ConfigSource): ConfigSourceSupplier { + return ConfigSourceSupplier(this, configSource, finalType, noDeprecation()).also { + suppliers += it + } } /** - * [LambdaSupplier]s don't require construction as they are entirely responsible for producing - * the value, so they have their own method + * Mark the source key of the value from this supplier as soft deprecated */ - fun retrieve(context: String, lambda: () -> T) { - suppliers += LambdaSupplier(context, lambda) + fun ConfigValueSupplier.softDeprecated(msg: String): ConfigValueSupplier { + suppliers -= this + return this.withDeprecation(softDeprecation(msg)).also { + suppliers += it + } } - fun onlyIf(condition: Condition, block: SupplierBuilder.() -> Unit) { - val supplier = SupplierBuilder(finalType).apply(block) - suppliers += ConditionalSupplier(condition, supplier.suppliers) + /** + * Mark the source key of the value from this supplier as hard deprecated + */ + fun ConfigValueSupplier.hardDeprecated(msg: String): ConfigValueSupplier { + suppliers -= this + return this.withDeprecation(hardDeprecation(msg)).also { + suppliers += it + } + } + + /** + * Add a value transformation operation + */ + fun ConfigValueSupplier.transformedBy(transformer: (T) -> T): ConfigValueSupplier { + suppliers -= this + return ValueTransformingSupplier(this, transformer).also { + suppliers += it + } } - fun onlyIf(context: String, predicate: () -> Boolean, block: SupplierBuilder.() -> Unit) = - onlyIf(Condition(context, predicate), block) /** - * Once the key and source are set, automatically set the inferred type. If the user wants to retrieve - * as a different type, they can call 'asType' on their own and override the inferred type. + * Add a type conversion operation. This is only able to be applied on top of a [ConfigSourceSupplier], because + * we need to recreate the underlying supplier to retrieve a different type than was originally inferred (we can + * add support for other suppliers where this makes sense as the need arises). */ - fun String.from(configSource: ConfigSource) = - ConfigPropertyState.Incomplete.Empty.lookup(this).from(configSource).asType(finalType) + inline fun ConfigSourceSupplier.convertFrom(noinline converter: (RetrieveType) -> T): TypeConvertingSupplier { + suppliers -= this + return TypeConvertingSupplier( + // Re-create the underyling ConfigSourceSupplier, but have it retrieve a different type + this.withRetrievedType(typeOf()), + converter + ).also { + suppliers += it + } + } + + /** + * Create a [LambdaSupplier] with a String context, a la: + * ...by config { + * ... + * "From an object" { MyFoo.port } + * ... + * } + * [LambdaSupplier]s don't require construction as they are entirely responsible for producing + * the value, so they have their own method + */ + operator fun String.invoke(lambda: () -> T) { + suppliers += LambdaSupplier(this, lambda) + } + + /** + * Wrap a set of inner suppliers in a condition guard + */ + fun onlyIf(context: String, predicate: () -> Boolean, block: SupplierBuilder.() -> Unit) { + val supplier = SupplierBuilder(finalType).apply(block) + suppliers += ConditionalSupplier(Condition(context, predicate), supplier.suppliers) + } } /** - * A standalone 'lookup' function which can be called to 'kick off' the construction of a [ConfigPropertyState] + * A standalone function which can be called to 'kick off' the construction of a [ConfigSourceSupplier] from + * a key and a [ConfigSource], a la: * - * This allows doing: - * val port: Int by config("app.server.port".from(configSource)) - * instead of - * val port: Int by config(lookup("app.server.port").from(configSource)) + * val port: Int by config("app.server.port".from(configSource)) */ -fun String.from(configSource: ConfigSource) = ConfigPropertyState.Incomplete.Empty.lookup(this).from(configSource) +inline fun String.from(configSource: ConfigSource) = + ConfigSourceSupplier(this, configSource, typeOf(), noDeprecation()) diff --git a/src/main/kotlin/org/jitsi/metaconfig/supplier/ConditionalSupplier.kt b/src/main/kotlin/org/jitsi/metaconfig/supplier/ConditionalSupplier.kt index 02aaba3..d4f1be4 100644 --- a/src/main/kotlin/org/jitsi/metaconfig/supplier/ConditionalSupplier.kt +++ b/src/main/kotlin/org/jitsi/metaconfig/supplier/ConditionalSupplier.kt @@ -18,6 +18,7 @@ package org.jitsi.metaconfig.supplier import org.jitsi.metaconfig.Condition import org.jitsi.metaconfig.ConfigException +import org.jitsi.metaconfig.Deprecation /** * A [ConfigValueSupplier] which searches through multiple inner [ConfigValueSupplier]s, in order, @@ -38,5 +39,8 @@ class ConditionalSupplier( } } + override fun withDeprecation(deprecation: Deprecation): LambdaSupplier = + throw Exception("ConditionalSupplier can't be marked as deprecated!") + override fun toString(): String = "${this::class.simpleName}: Enabled only when ${condition.context}: $innerSupplier" } diff --git a/src/main/kotlin/org/jitsi/metaconfig/supplier/ConfigSourceSupplier.kt b/src/main/kotlin/org/jitsi/metaconfig/supplier/ConfigSourceSupplier.kt index 58d82da..4cf1749 100644 --- a/src/main/kotlin/org/jitsi/metaconfig/supplier/ConfigSourceSupplier.kt +++ b/src/main/kotlin/org/jitsi/metaconfig/supplier/ConfigSourceSupplier.kt @@ -55,6 +55,13 @@ class ConfigSourceSupplier( } } + /** + * Return a new [ConfigSourceSupplier] with the same key, source and deprecation but which + * retrieves as [newType] instead of [type]. + */ + fun withRetrievedType(newType: KType): ConfigSourceSupplier = + ConfigSourceSupplier(key, source, newType, deprecation) + override fun withDeprecation(deprecation: Deprecation): ConfigValueSupplier = ConfigSourceSupplier(key, source, type, deprecation) diff --git a/src/main/kotlin/org/jitsi/metaconfig/supplier/ConfigValueSupplier.kt b/src/main/kotlin/org/jitsi/metaconfig/supplier/ConfigValueSupplier.kt index d03f5a5..aa7e4c5 100644 --- a/src/main/kotlin/org/jitsi/metaconfig/supplier/ConfigValueSupplier.kt +++ b/src/main/kotlin/org/jitsi/metaconfig/supplier/ConfigValueSupplier.kt @@ -29,13 +29,12 @@ abstract class ConfigValueSupplier { fun get(): ValueType = value /** - * Apply a [Deprecation] to this [ConfigValueSupplier]. By default it does nothing. This should - * only be overridden by classes which retrieve properties from some "source" (e.g. a file). - * Suppliers which wrap another an do some kind of transformation, for example, - * shouldn't override this. - * + * Apply a [Deprecation] to this [ConfigValueSupplier]. Deprecation is only applied to types + * where it makes sense (those which retrieve a value from an 'external' location--right now + * only [ConfigSourceSupplier]), however, types which wrap an inner supplier must pass the + * deprecation 'down' so it can be applied correctly by any supplier which should observe it. */ - open fun withDeprecation(deprecation: Deprecation): ConfigValueSupplier = this + abstract fun withDeprecation(deprecation: Deprecation): ConfigValueSupplier /** * Get the value from this supplier. Throws [ConfigException.UnableToRetrieve] diff --git a/src/main/kotlin/org/jitsi/metaconfig/supplier/FallbackSupplier.kt b/src/main/kotlin/org/jitsi/metaconfig/supplier/FallbackSupplier.kt index e4a4e41..fe7b2d4 100644 --- a/src/main/kotlin/org/jitsi/metaconfig/supplier/FallbackSupplier.kt +++ b/src/main/kotlin/org/jitsi/metaconfig/supplier/FallbackSupplier.kt @@ -17,6 +17,7 @@ package org.jitsi.metaconfig.supplier import org.jitsi.metaconfig.ConfigException +import org.jitsi.metaconfig.Deprecation import org.jitsi.metaconfig.MetaconfigSettings import org.jitsi.metaconfig.noDeprecation @@ -58,6 +59,9 @@ class FallbackSupplier( ) } + override fun withDeprecation(deprecation: Deprecation): FallbackSupplier = + FallbackSupplier(suppliers.map { it.withDeprecation(deprecation) }) + override fun toString(): String = "${this::class.simpleName}: checking suppliers:" + suppliers.joinToString(prefix = "\n ", separator = "\n ") } diff --git a/src/main/kotlin/org/jitsi/metaconfig/supplier/LambdaSupplier.kt b/src/main/kotlin/org/jitsi/metaconfig/supplier/LambdaSupplier.kt index fb6f178..90311fd 100644 --- a/src/main/kotlin/org/jitsi/metaconfig/supplier/LambdaSupplier.kt +++ b/src/main/kotlin/org/jitsi/metaconfig/supplier/LambdaSupplier.kt @@ -17,6 +17,7 @@ package org.jitsi.metaconfig.supplier import org.jitsi.metaconfig.ConfigException +import org.jitsi.metaconfig.Deprecation import org.jitsi.metaconfig.MetaconfigSettings class LambdaSupplier( @@ -42,5 +43,8 @@ class LambdaSupplier( } } + override fun withDeprecation(deprecation: Deprecation): LambdaSupplier = + throw Exception("LambdaSupplier can't be marked as deprecated!") + override fun toString(): String = "${this::class.simpleName}${if (context.isNotBlank()) ": '$context'" else ""}" } diff --git a/src/main/kotlin/org/jitsi/metaconfig/supplier/TypeConvertingSupplier.kt b/src/main/kotlin/org/jitsi/metaconfig/supplier/TypeConvertingSupplier.kt index aeaac30..934192e 100644 --- a/src/main/kotlin/org/jitsi/metaconfig/supplier/TypeConvertingSupplier.kt +++ b/src/main/kotlin/org/jitsi/metaconfig/supplier/TypeConvertingSupplier.kt @@ -16,6 +16,7 @@ package org.jitsi.metaconfig.supplier +import org.jitsi.metaconfig.Deprecation import org.jitsi.metaconfig.MetaconfigSettings import org.jitsi.metaconfig.noDeprecation @@ -39,5 +40,9 @@ class TypeConvertingSupplier( } } + override fun withDeprecation(deprecation: Deprecation): TypeConvertingSupplier { + return TypeConvertingSupplier(originalSupplier.withDeprecation(deprecation), converter) + } + override fun toString(): String = "${this::class.simpleName}: converting value from $originalSupplier" } diff --git a/src/main/kotlin/org/jitsi/metaconfig/supplier/ValueTransformingSupplier.kt b/src/main/kotlin/org/jitsi/metaconfig/supplier/ValueTransformingSupplier.kt index 377028a..628f041 100644 --- a/src/main/kotlin/org/jitsi/metaconfig/supplier/ValueTransformingSupplier.kt +++ b/src/main/kotlin/org/jitsi/metaconfig/supplier/ValueTransformingSupplier.kt @@ -16,6 +16,7 @@ package org.jitsi.metaconfig.supplier +import org.jitsi.metaconfig.Deprecation import org.jitsi.metaconfig.MetaconfigSettings import org.jitsi.metaconfig.noDeprecation @@ -38,5 +39,9 @@ class ValueTransformingSupplier( } } + override fun withDeprecation(deprecation: Deprecation): ValueTransformingSupplier { + return ValueTransformingSupplier(originalSupplier.withDeprecation(deprecation), transformer) + } + override fun toString(): String = "${this::class.simpleName}: transforming value from $originalSupplier" } diff --git a/src/test/kotlin/org/jitsi/metaconfig/ConfigPropertyStateTest.kt b/src/test/kotlin/org/jitsi/metaconfig/ConfigPropertyBuildingTest.kt similarity index 81% rename from src/test/kotlin/org/jitsi/metaconfig/ConfigPropertyStateTest.kt rename to src/test/kotlin/org/jitsi/metaconfig/ConfigPropertyBuildingTest.kt index a61a75b..9f22d42 100644 --- a/src/test/kotlin/org/jitsi/metaconfig/ConfigPropertyStateTest.kt +++ b/src/test/kotlin/org/jitsi/metaconfig/ConfigPropertyBuildingTest.kt @@ -21,7 +21,7 @@ import io.kotest.core.spec.style.ShouldSpec import io.kotest.matchers.shouldBe import java.time.Duration -class ConfigPropertyStateTest : ShouldSpec({ +class ConfigPropertyBuildingTest : ShouldSpec({ val legacyConfig = MapConfigSource("legacy config") { put("legacy.num", 42) put("legacy.interval", 5000L) @@ -39,13 +39,17 @@ class ConfigPropertyStateTest : ShouldSpec({ } should("allow marking a property with a key and a source as soft deprecated") { val obj = object { - val num: Int by config("legacy.num".from(legacyConfig).softDeprecated("use new.num")) + val num: Int by config { + "legacy.num".from(legacyConfig).softDeprecated("use new.num") + } } obj.num shouldBe 42 } should("allow marking a property with a key and a source as hard deprecated") { val obj = object { - val num: Int by config("legacy.num".from(legacyConfig).hardDeprecated("use new.num")) + val num: Int by config { + "legacy.num".from(legacyConfig).hardDeprecated("use new.num") + } } shouldThrow { obj.num @@ -54,7 +58,7 @@ class ConfigPropertyStateTest : ShouldSpec({ should("allow marking a property with a key, a source and a transformation as hard deprecated") { val obj = object { val num: Int by config { - retrieve("legacy.num".from(legacyConfig).andTransformBy { it + 1 }.hardDeprecated("use new.num")) + "legacy.num".from(legacyConfig).transformedBy { it + 1 }.hardDeprecated("use new.num") } } shouldThrow { @@ -64,7 +68,7 @@ class ConfigPropertyStateTest : ShouldSpec({ should("allow transforming the value of a property") { val obj = object { val num: Int by config { - retrieve("legacy.num".from(legacyConfig).andTransformBy { it / 2 }) + "legacy.num".from(legacyConfig).transformedBy { it / 2 } } } obj.num shouldBe 21 @@ -72,7 +76,7 @@ class ConfigPropertyStateTest : ShouldSpec({ should("allow converting the type of a property") { val obj = object { val interval: Duration by config { - retrieve("legacy.interval".from(legacyConfig).asType().andConvertBy(Duration::ofMillis)) + "legacy.interval".from(legacyConfig).convertFrom(Duration::ofMillis) } } obj.interval shouldBe Duration.ofMillis(5000) @@ -80,8 +84,8 @@ class ConfigPropertyStateTest : ShouldSpec({ should("allow falling back across multiple properties") { val obj = object { val num: Int by config { - retrieve("some.missing.path".from(legacyConfig)) - retrieve("new.num".from(newConfig)) + "some.missing.path".from(legacyConfig) + "new.num".from(newConfig) } } obj.num shouldBe 43 @@ -90,12 +94,12 @@ class ConfigPropertyStateTest : ShouldSpec({ val obj = object { val enabledNum: Int by config { onlyIf("enabled", { true }) { - retrieve("new.num".from(newConfig)) + "new.num".from(newConfig) } } val disabledNum: Int by config { onlyIf("enabled", { false} ) { - retrieve("new.num".from(newConfig)) + "new.num".from(newConfig) } } } @@ -108,7 +112,7 @@ class ConfigPropertyStateTest : ShouldSpec({ val obj = object { val num: Int? by optionalconfig { onlyIf("enabled", { false }) { - retrieve("new.num".from(newConfig)) + "new.num".from(newConfig) } } } @@ -120,7 +124,7 @@ class ConfigPropertyStateTest : ShouldSpec({ val obj = object { val num: Int? by optionalconfig { onlyIf("enabled", { true }) { - retrieve("missing.num".from(newConfig)) + "missing.num".from(newConfig) } } } diff --git a/src/test/kotlin/org/jitsi/metaconfig/FallbackPropertyTest.kt b/src/test/kotlin/org/jitsi/metaconfig/FallbackPropertyTest.kt index b066e87..8f91dca 100644 --- a/src/test/kotlin/org/jitsi/metaconfig/FallbackPropertyTest.kt +++ b/src/test/kotlin/org/jitsi/metaconfig/FallbackPropertyTest.kt @@ -27,8 +27,8 @@ class FallbackPropertyTest : ShouldSpec({ context("a class with a fallback property") { val obj = object { val enabled: Boolean by config { - retrieve("old.path.enabled".from(firstConfigSource)) - retrieve("server.enabled".from(secondConfigSource)) + "old.path.enabled".from(firstConfigSource) + "server.enabled".from(secondConfigSource) } } context("when the property is present in the first source") { diff --git a/src/test/kotlin/org/jitsi/metaconfig/TransformingPropertyTest.kt b/src/test/kotlin/org/jitsi/metaconfig/TransformingPropertyTest.kt index c126316..8baef0c 100644 --- a/src/test/kotlin/org/jitsi/metaconfig/TransformingPropertyTest.kt +++ b/src/test/kotlin/org/jitsi/metaconfig/TransformingPropertyTest.kt @@ -26,7 +26,7 @@ class TransformingPropertyTest : ShouldSpec({ context("a class with a property whose value is transformed") { val obj = object { val enabled: Boolean by config { - retrieve("disabled".from(configSrc).andTransformBy { !it }) + "disabled".from(configSrc).transformedBy { !it } } } context("when the property is present in the config source") { diff --git a/src/test/kotlin/org/jitsi/metaconfig/TypeConvertingPropertyTest.kt b/src/test/kotlin/org/jitsi/metaconfig/TypeConvertingPropertyTest.kt index 4306e06..d9c0aaa 100644 --- a/src/test/kotlin/org/jitsi/metaconfig/TypeConvertingPropertyTest.kt +++ b/src/test/kotlin/org/jitsi/metaconfig/TypeConvertingPropertyTest.kt @@ -26,7 +26,7 @@ class TypeConvertingPropertyTest : ShouldSpec({ context("a class with a proeprty whose type is converted") { val obj = object { val duration: Duration by config { - retrieve("interval".from(configSrc).asType().andConvertBy(Duration::ofMillis)) + "interval".from(configSrc).convertFrom(Duration::ofMillis) } } context("when the property is present in the config") {