-
Notifications
You must be signed in to change notification settings - Fork 2
Add CheckpointingFileDataSupplier - can lock DSL values #31
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| package net.devslash | ||
|
|
||
| interface AcceptCallContext<T> { | ||
| fun inject(): CallBuilder<T>.() -> Unit | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,6 +3,7 @@ package net.devslash | |
| import net.devslash.err.RetryOnTransitiveError | ||
| import java.time.Duration | ||
| import java.util.* | ||
| import kotlin.reflect.KMutableProperty0 | ||
|
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. snip |
||
|
|
||
| /** | ||
| * Version contains the current version defined in the build.gradle root file. | ||
|
|
@@ -43,12 +44,12 @@ enum class HttpMethod { | |
| @FetchDSL | ||
| @Suppress("MemberVisibilityCanBePrivate") | ||
| open class CallBuilder<T>(private val url: String) { | ||
| var urlProvider: URLProvider? = null | ||
| var data: RequestDataSupplier<T>? = null | ||
| var body: HttpBody? = null | ||
| var type: HttpMethod = HttpMethod.GET | ||
| var urlProvider: URLProvider? by LockableValue(null) | ||
| var data: RequestDataSupplier<T>? by LockableValue(null) | ||
| var body: HttpBody? by LockableValue(null) | ||
| var type: HttpMethod by LockableValue(HttpMethod.GET) | ||
| var headers: Map<String, List<Any>> = mapOf() | ||
| var onError: OnError? = RetryOnTransitiveError() | ||
| var onError: OnError? by LockableValue(RetryOnTransitiveError()) | ||
|
|
||
| private var preHooksList = mutableListOf<BeforeHook>() | ||
| private var postHooksList = mutableListOf<AfterHook>() | ||
|
|
@@ -65,6 +66,10 @@ open class CallBuilder<T>(private val url: String) { | |
| body = BodyBuilder().apply(block).build() | ||
| } | ||
|
|
||
| fun inject(x: AcceptCallContext<T>) { | ||
| this.apply(x.inject()) | ||
| } | ||
|
|
||
| private fun mapHeaders(map: Map<String, List<Any>>): Map<String, List<HeaderValue>> { | ||
| return map.mapValues { entry -> | ||
| entry.value.map { value -> | ||
|
|
@@ -87,6 +92,14 @@ open class CallBuilder<T>(private val url: String) { | |
| preHooksList, postHooksList | ||
| ) | ||
| } | ||
|
|
||
| fun <T> lock(field: KMutableProperty0<RequestDataSupplier<T>?>) { | ||
|
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. document |
||
| when (val delegate = field.getDelegate()) { | ||
| is LockableValue<*, *> -> { | ||
| delegate.lock() | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| fun replaceString(changes: Map<String, String>, str: String): String { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| package net.devslash | ||
|
|
||
| import java.util.concurrent.atomic.AtomicBoolean | ||
| import kotlin.properties.ReadWriteProperty | ||
| import kotlin.reflect.KProperty | ||
|
|
||
| annotation class DSLLockedValue | ||
|
|
||
| /** | ||
| * Lockable value is a delegate pattern that allows for a marker annotation to be used to lock. | ||
| * | ||
| * Doesn't work on primitives. If you want it to work on a primitive, then wrap it in a data class for now | ||
|
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| */ | ||
| class LockableValue<R, T>(private var curValue: T) : ReadWriteProperty<R, T> { | ||
| private val locked = AtomicBoolean(false) | ||
|
|
||
| override fun getValue(thisRef: R, property: KProperty<*>): T { | ||
| return curValue | ||
| } | ||
|
|
||
| override fun setValue(thisRef: R, property: KProperty<*>, value: T) { | ||
|
|
||
| val annotation = if (value != null) value!!::class.java.getAnnotation(DSLLockedValue::class.java) else null | ||
| if (annotation != null) { | ||
| // Then we have someone who wants to be protected. Therefore lock it down | ||
| // Unless it's already set, in which complain | ||
| if (locked.get() && curValue != null) { | ||
| throw AlreadySetException(property, curValue) | ||
| } | ||
| if (locked.compareAndSet(false, true)) { | ||
| curValue = value | ||
| return | ||
| } else { | ||
| throw AlreadySetException(property, curValue) | ||
| } | ||
| } | ||
|
|
||
| // If we've already locked. Also throw | ||
| if (locked.get()) { | ||
| throw AlreadySetException(property, curValue) | ||
| } | ||
|
|
||
| // Otherwise we can set | ||
| curValue = value | ||
| } | ||
|
|
||
| fun lock() { | ||
| if (!locked.compareAndSet(false, true)) { | ||
| throw AlreadySetException2() | ||
| } | ||
| } | ||
|
|
||
| class AlreadySetException2 : | ||
|
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. snip |
||
| RuntimeException() | ||
|
|
||
| class AlreadySetException(kProperty: KProperty<*>, value: Any?) : | ||
| RuntimeException("Property \"${kProperty.name}\" has already been set to ${value}. Cannot be set again") | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,13 +5,11 @@ import io.ktor.response.* | |
| import io.ktor.routing.* | ||
| import io.ktor.server.engine.* | ||
| import io.ktor.server.netty.* | ||
| import net.devslash.action | ||
| import net.devslash.* | ||
| import net.devslash.data.FileDataSupplier | ||
| import net.devslash.data.ListDataSupplier | ||
| import net.devslash.mustGet | ||
| import net.devslash.outputs.WriteFile | ||
| import net.devslash.pipes.ResettablePipe | ||
| import net.devslash.runHttp | ||
| import java.net.ServerSocket | ||
| import java.nio.file.Files | ||
| import java.util.concurrent.TimeUnit | ||
|
|
@@ -59,6 +57,7 @@ fun main() { | |
| } | ||
| call<Int>(address) { | ||
| data = ListDataSupplier(listOf(1, 2, 3)) | ||
| lock(this::data) | ||
|
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. change to specific example |
||
| before { | ||
| action { | ||
| println(data.mustGet<Int>()) | ||
|
|
@@ -73,3 +72,9 @@ fun main() { | |
| server.stop(10, 10, TimeUnit.MILLISECONDS) | ||
| } | ||
| } | ||
|
|
||
|
|
||
| @DSLLockedValue | ||
| class ProtectedDS<T>(val ds: RequestDataSupplier<T>) : RequestDataSupplier<T> { | ||
| override suspend fun getDataForRequest(): RequestData? = ds.getDataForRequest() | ||
| } | ||
|
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. line |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| package net.devslash | ||
|
|
||
| import org.hamcrest.CoreMatchers.* | ||
| import org.hamcrest.MatcherAssert.assertThat | ||
| import org.junit.Assert.assertThrows | ||
| import org.junit.Test | ||
|
|
||
| internal class LockableValueTest { | ||
|
|
||
| // What i can do is have `lock` as a | ||
|
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. snip |
||
| @DSLLockedValue | ||
| data class Locked(val b: Boolean) | ||
| data class Unlocked(val b: Boolean) | ||
|
|
||
| @Test | ||
| fun testLockWillAcceptNullable() { | ||
| val x: Int? by LockableValue(null) | ||
| assertThat(x, `is`(nullValue())) | ||
| } | ||
|
|
||
| @Test | ||
| fun testLockSetOnAnnotated() { | ||
| var x: Locked? by LockableValue(null) | ||
| x = Locked(true) | ||
| assertThrows(LockableValue.AlreadySetException::class.java) { | ||
| x = Locked(false) | ||
| } | ||
| assertThat(x, equalTo(Locked(true))) | ||
| } | ||
|
|
||
| @Test | ||
| fun testNotLockedTillFirstAnnotatedValue() { | ||
| var x: Any? by LockableValue(null) | ||
| x = Unlocked(false) | ||
| x = Locked(true) | ||
| assertThrows(LockableValue.AlreadySetException::class.java) { | ||
| x = Unlocked(true) | ||
| } | ||
| assertThat(x, equalTo(Locked(true))) | ||
| } | ||
|
|
||
| @Test | ||
| fun testLockDoesNotSetOnUnannotated() { | ||
| var x: Unlocked? by LockableValue(null) | ||
| x = Unlocked(true) | ||
| x = Unlocked(false) | ||
| assertThat(x, equalTo(Unlocked(false))) | ||
|
|
||
| x = null | ||
| assertThat(x, `is`(nullValue())) | ||
| } | ||
| } | ||
|
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fix |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,10 +2,7 @@ package net.devslash.data | |
|
|
||
| import kotlinx.coroutines.ExperimentalCoroutinesApi | ||
| import kotlinx.coroutines.test.runBlockingTest | ||
| import net.devslash.Call | ||
| import net.devslash.CallBuilder | ||
| import net.devslash.HttpResponse | ||
| import net.devslash.mustGet | ||
| import net.devslash.* | ||
| import net.devslash.outputs.LogResponse | ||
| import net.devslash.util.basicRequest | ||
| import net.devslash.util.basicResponse | ||
|
|
@@ -69,7 +66,7 @@ internal class CheckpointingFileDataSupplierTest { | |
| val call: Call<List<String>> | ||
| supplier.use { | ||
| call = CallBuilder<List<String>>("http://example.com").apply { | ||
| it.inject(this) | ||
| inject(supplier) | ||
| after { | ||
| // Test append, not overwrite | ||
| +LogResponse() | ||
|
|
@@ -96,6 +93,12 @@ internal class CheckpointingFileDataSupplierTest { | |
| val rd = supplier.getDataForRequest()!! | ||
| val rd2 = supplier.getDataForRequest()!! | ||
|
|
||
| runHttp { | ||
|
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fix |
||
| call("T") { | ||
| inject(supplier) | ||
| } | ||
| } | ||
|
|
||
| // In this form, 200 is failure | ||
| supplier.accept(basicRequest(), basicResponse(), rd) | ||
| supplier.accept(basicRequest(), HttpResponse(URI(basicUrl), 404, mapOf(), ByteArray(0)), rd2) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
snip?