Skip to content

Commit

Permalink
feature: support to cast config to value
Browse files Browse the repository at this point in the history
  • Loading branch information
uchuhimo committed Sep 14, 2019
1 parent 3408d2b commit 51a8d8e
Show file tree
Hide file tree
Showing 18 changed files with 337 additions and 110 deletions.
37 changes: 31 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ compile(group = "com.github.uchuhimo.konf", name = "konf", version = "master-SNA

```kotlin
val config = Config { addSpec(ServerSpec) }
.from.yaml.file("/path/to/server.yml")
.from.yaml.file("server.yml")
.from.json.resource("server.json")
.from.env()
.from.systemProperties()
Expand All @@ -177,27 +177,27 @@ compile(group = "com.github.uchuhimo.konf", name = "konf", version = "master-SNA

```kotlin
val config = Config { addSpec(ServerSpec) }.withSource(
Source.from.yaml.file("/path/to/server.yml") +
Source.from.yaml.file("server.yml") +
Source.from.json.resource("server.json") +
Source.from.env() +
Source.from.systemProperties()
)
```

This config contains all items defined in `ServerSpec`, and load values from 4 different sources. Values in resource file `server.json` will override those in file `/path/to/server.yml`, values from system environment will override those in `server.json`, and values from system properties will override those from system environment.
This config contains all items defined in `ServerSpec`, and load values from 4 different sources. Values in resource file `server.json` will override those in file `server.yml`, values from system environment will override those in `server.json`, and values from system properties will override those from system environment.

If you want to watch file `/path/to/server.yml` and reload values when file content is changed, you can use `watchFile` instead of `file`:
If you want to watch file `server.yml` and reload values when file content is changed, you can use `watchFile` instead of `file`:

```kotlin
val config = Config { addSpec(ServerSpec) }
.from.yaml.watchFile("/path/to/server.yml")
.from.yaml.watchFile("server.yml")
.from.json.resource("server.json")
.from.env()
.from.systemProperties()
```

3. Define values in source. You can define in any of these sources:
- in `/path/to/server.yml`:
- in `server.yml`:
```yaml
server:
host: 0.0.0.0
Expand All @@ -224,10 +224,27 @@ compile(group = "com.github.uchuhimo.konf", name = "konf", version = "master-SNA

4. Retrieve values from config with type-safe APIs:
```kotlin
data class Server(val host: String, val port: Int) {
fun start() {}
}

val server = Server(config[ServerSpec.host], config[ServerSpec.port])
server.start()
```

5. Retrieve values from multiple sources without using config spec:

```kotlin
val server = Config()
.from.yaml.file("server.yml")
.from.json.resource("server.json")
.from.env()
.from.systemProperties()
.at("server")
.toValue<Server>()
server.start()
```

## Define items

Config items is declared in config spec, added to config by `Config#addSpec`. All items in same config spec have same prefix. Define a config spec with prefix `local.server`:
Expand Down Expand Up @@ -358,6 +375,14 @@ or:
val host = config<String>("server.host")
```

### Cast config to value

Cast config to a value with the target type:

```kotlin
val server = config.toValue<Server>()
```

### Check whether an item exists in config or not

Check whether an item exists in config or not:
Expand Down
2 changes: 2 additions & 0 deletions konf-all/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,6 @@ dependencies {

val main by sourceSets
snippetImplementation(main.output)
val snippet by sourceSets
testImplementation(snippet.output)
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,33 +20,52 @@ import com.uchuhimo.konf.Config
import com.uchuhimo.konf.ConfigSpec
import com.uchuhimo.konf.source.Source
import com.uchuhimo.konf.source.yaml
import com.uchuhimo.konf.toValue
import java.io.File

object ServerSpec : ConfigSpec() {
val host by optional("0.0.0.0")
val port by required<Int>()
}

fun main(args: Array<String>) {
val file = File("server.yml")
file.writeText("""
server:
host: 127.0.0.1
port: 8080
""".trimIndent())
file.deleteOnExit()
val config = Config { addSpec(ServerSpec) }
.from.yaml.file("/path/to/server.yml")
.from.yaml.file("server.yml")
.from.json.resource("server.json")
.from.env()
.from.systemProperties()
run {
val config = Config { addSpec(ServerSpec) }.withSource(
Source.from.yaml.file("/path/to/server.yml") +
Source.from.yaml.file("server.yml") +
Source.from.json.resource("server.json") +
Source.from.env() +
Source.from.systemProperties()
)
}
run {
val config = Config { addSpec(ServerSpec) }
.from.yaml.watchFile("/path/to/server.yml")
.from.yaml.watchFile("server.yml")
.from.json.resource("server.json")
.from.env()
.from.systemProperties()
}
val server = Server(config[ServerSpec.host], config[ServerSpec.port])
server.start()
run {
val server = Config()
.from.yaml.file("server.yml")
.from.json.resource("server.json")
.from.env()
.from.systemProperties()
.at("server")
.toValue<Server>()
server.start()
}
}
6 changes: 6 additions & 0 deletions konf-all/src/snippet/resources/server.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"server": {
"host": "127.0.0.1",
"port": 8080
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
* Copyright 2017-2019 the original author or authors.
*
* 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 com.uchuhimo.konf.source

import com.natpryce.hamkrest.assertion.assertThat
import com.natpryce.hamkrest.equalTo
import com.uchuhimo.konf.Config
import com.uchuhimo.konf.snippet.Server
import com.uchuhimo.konf.snippet.ServerSpec
import com.uchuhimo.konf.toValue
import org.jetbrains.spek.api.Spek
import org.jetbrains.spek.api.dsl.it
import org.jetbrains.spek.api.dsl.on
import java.io.File

object QuickStartSpec : Spek({
on("use default loaders") {
val config = useFile {
Config { addSpec(ServerSpec) }
.from.yaml.file("server.yml")
.from.json.resource("server.json")
.from.env()
.from.systemProperties()
}
it("should load all values") {
assertThat(config.toMap(),
equalTo(mapOf("server.host" to "127.0.0.1", "server.port" to 8080)))
}
}
on("use default providers") {
val config = useFile {
Config { addSpec(ServerSpec) }.withSource(
Source.from.yaml.file("server.yml") +
Source.from.json.resource("server.json") +
Source.from.env() +
Source.from.systemProperties()
)
}
it("should load all values") {
assertThat(config.toMap(),
equalTo(mapOf("server.host" to "127.0.0.1", "server.port" to 8080)))
}
}
on("watch file") {
val config = useFile {
Config { addSpec(ServerSpec) }
.from.yaml.watchFile("server.yml")
.from.json.resource("server.json")
.from.env()
.from.systemProperties()
}
it("should load all values") {
assertThat(config.toMap(),
equalTo(mapOf("server.host" to "127.0.0.1", "server.port" to 8080)))
}
}
on("cast to value") {
val config = useFile {
Config()
.from.yaml.file("server.yml")
.from.json.resource("server.json")
.from.env()
.from.systemProperties()
.at("server")
}
val server = config.toValue<Server>()
it("should load all values") {
assertThat(server, equalTo(Server(host = "127.0.0.1", port = 8080)))
}
}
})

private fun <T> useFile(block: () -> T): T {
val file = File("server.yml")
file.writeText("""
server:
host: 127.0.0.1
port: 8080
""".trimIndent())
try {
return block()
} finally {
file.delete()
}
}
23 changes: 15 additions & 8 deletions konf-core/src/main/kotlin/com/uchuhimo/konf/BaseConfig.kt
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ open class BaseConfig(
private val specsInLayer: MutableList<Spec> = mutableListOf(),
private val featuresInLayer: MutableMap<Feature, Boolean> = mutableMapOf(),
private val nodeByItem: MutableMap<Item<*>, ItemNode> = mutableMapOf(),
private val tree: TreeNode = ContainerNode.empty(),
private val tree: TreeNode = ContainerNode.placeHolder(),
private val hasChildren: Value<Boolean> = Value(false),
private val lock: ReentrantReadWriteLock = ReentrantReadWriteLock()
) : Config {
Expand All @@ -68,25 +68,26 @@ open class BaseConfig(
if (path.isEmpty()) {
return this
} else {
val originalConfig = this
return object : BaseConfig(
name = name,
parent = parent?.at(path) as BaseConfig?,
mapper = mapper,
specsInLayer = specsInLayer,
featuresInLayer = featuresInLayer,
nodeByItem = nodeByItem,
tree = tree.getOrNull(path) ?: ContainerNode.empty().also {
tree = tree.getOrNull(path) ?: ContainerNode.placeHolder().also {
lock.write { tree[path] = it }
},
hasChildren = hasChildren,
lock = lock
) {
override val source: Source
get() {
if (path !in this@BaseConfig.source) {
this@BaseConfig.source.tree[path] = ContainerNode.empty()
if (path !in originalConfig.source) {
originalConfig.source.tree[path] = ContainerNode.placeHolder()
}
return this@BaseConfig.source[path]
return originalConfig.source[path]
}
}
}
Expand All @@ -96,6 +97,7 @@ open class BaseConfig(
if (prefix.isEmpty()) {
return this
} else {
val originalConfig = this
return object : BaseConfig(
name = name,
parent = parent?.withPrefix(prefix) as BaseConfig?,
Expand All @@ -104,11 +106,13 @@ open class BaseConfig(
featuresInLayer = featuresInLayer,
nodeByItem = nodeByItem,
tree = if (prefix.isEmpty()) tree
else ContainerNode.empty().apply { set(prefix, tree) },
else ContainerNode.empty().apply {
set(prefix, tree)
},
hasChildren = hasChildren,
lock = lock
) {
override val source: Source get() = this@BaseConfig.source.withPrefix(prefix)
override val source: Source get() = originalConfig.source.withPrefix(prefix)
}
}
}
Expand Down Expand Up @@ -303,7 +307,7 @@ open class BaseConfig(
return if (this is LeafNode) {
true
} else if (path.isEmpty()) {
children.isNotEmpty()
!isEmpty()
} else {
val key = path.first()
val rest = path.drop(1)
Expand Down Expand Up @@ -424,6 +428,9 @@ open class BaseConfig(
lock.write {
nodeByItem.clear()
tree.children.clear()
if (tree is MapNode) {
tree.isPlaceHolder = true
}
}
}

Expand Down
15 changes: 12 additions & 3 deletions konf-core/src/main/kotlin/com/uchuhimo/konf/Config.kt
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,17 @@ interface Config : ItemContainer {
* @return a property that can read/set associated value casted from config
*/
inline fun <reified T> Config.cast() =
object : RequiredConfigProperty<T>(this.withPrefix("root"), name = "root") {}
object : RequiredConfigProperty<T>(this.withPrefix("root").withLayer(), name = "root") {}

/**
* Returns a value casted from config.
*
* @return a value casted from config
*/
inline fun <reified T> Config.toValue(): T {
val value by cast<T>()
return value
}

/**
* Returns a property that can read/set associated value for specified required item.
Expand Down Expand Up @@ -383,8 +393,7 @@ open class RequiredConfigProperty<T>(
private val type: JavaType = TypeFactory.defaultInstance().constructType(this::class.java)
.findSuperType(RequiredConfigProperty::class.java).bindings.typeParameters[0]

operator fun provideDelegate(thisRef: Any?, property: KProperty<*>):
ReadWriteProperty<Any?, T> {
operator fun provideDelegate(thisRef: Any?, property: KProperty<*>): ReadWriteProperty<Any?, T> {
val item = object : RequiredItem<T>(Spec.dummy, name
?: property.name, description, type, nullable) {}
config.addItem(item, prefix)
Expand Down
Loading

0 comments on commit 51a8d8e

Please sign in to comment.