diff --git a/api/src/main/kotlin/net/devslash/BodyProviders.kt b/api/src/main/kotlin/net/devslash/BodyProviders.kt index 3d919fa..0066529 100644 --- a/api/src/main/kotlin/net/devslash/BodyProviders.kt +++ b/api/src/main/kotlin/net/devslash/BodyProviders.kt @@ -1,7 +1,7 @@ package net.devslash -class BasicBodyProvider(private val body: String, val data: RequestData) : BodyProvider { +class BasicBodyProvider(private val body: String, val data: RequestData) : BodyProvider { fun get(): String { var copy = "" + body data.getReplacements().forEach { (key, value) -> copy = copy.replace(key, value) } @@ -9,11 +9,13 @@ class BasicBodyProvider(private val body: String, val data: RequestData) : BodyP } } -class FormBody(private val body: Map>, - private val data: RequestData) : BodyProvider { +class FormBody( + private val body: Map>, + private val data: RequestData +) : BodyProvider { fun get(): Map> { return body.map { - val entries = it.value.map { it.asReplaceableValue().get(data) } + val entries = it.value.map { v -> data.accept(v) } it.key to entries }.toMap() } @@ -25,7 +27,7 @@ class JsonBody(private val any: Any) : BodyProvider { } } -fun getBodyProvider(call: Call, data: RequestData): BodyProvider { +fun getBodyProvider(call: Call, data: RequestData): BodyProvider { if (call.body == null) { return EmptyBodyProvider } diff --git a/api/src/main/kotlin/net/devslash/CookieJar.kt b/api/src/main/kotlin/net/devslash/CookieJar.kt index 07a6efd..4d97721 100644 --- a/api/src/main/kotlin/net/devslash/CookieJar.kt +++ b/api/src/main/kotlin/net/devslash/CookieJar.kt @@ -6,7 +6,7 @@ import java.util.concurrent.ConcurrentHashMap class CookieJar : SimpleBeforeHook, SimpleAfterHook { private val cookies = ConcurrentHashMap(mutableMapOf>()) - override fun accept(req: HttpRequest, data: RequestData) { + override fun accept(req: HttpRequest, data: RequestData) { val basicURl = URL(req.url) val filteredUrl = "${basicURl.protocol}://${basicURl.host}" diff --git a/api/src/main/kotlin/net/devslash/Definitions.kt b/api/src/main/kotlin/net/devslash/Definitions.kt index 2d9fe26..ea98977 100644 --- a/api/src/main/kotlin/net/devslash/Definitions.kt +++ b/api/src/main/kotlin/net/devslash/Definitions.kt @@ -4,26 +4,26 @@ import kotlinx.coroutines.channels.Channel sealed class Value data class StrValue(val value: String) : Value() -data class ProvidedValue(val lambda: (RequestData) -> String) : Value() +data class ProvidedValue(val lambda: (RequestData) -> String) : Value() interface BodyProvider -data class Session(val calls: List, val concurrency: Int = 100, val delay: Long?, val rateOptions: RateLimitOptions) +data class Session(val calls: List>, val concurrency: Int = 100, val delay: Long?, val rateOptions: RateLimitOptions) -data class Call(val url: String, +data class Call(val url: String, val headers: Map>?, val cookieJar: String?, val type: HttpMethod, - val dataSupplier: RequestDataSupplier?, - val body: HttpBody?, + val dataSupplier: RequestDataSupplier?, + val body: HttpBody?, val onError: OnError?, val beforeHooks: List, val afterHooks: List) -interface RequestDataSupplier { +interface RequestDataSupplier { /** * Request data should be a closure that is safe to call on a per-request basis */ - suspend fun getDataForRequest(): RequestData? + suspend fun getDataForRequest(): RequestData? fun init() { // By default this is empty, but implementors can be assured that on a per-call basis, this @@ -32,29 +32,33 @@ interface RequestDataSupplier { } interface OutputFormat { - fun accept(resp: HttpResponse, rep: RequestData): ByteArray? + fun accept(resp: HttpResponse, rep: RequestData): ByteArray? } -interface RequestData { +interface RequestData { + @Deprecated("Instead, please migrate to utilising Get") fun getReplacements(): Map + fun get(): T + fun accept(v: String): String } interface BasicOutput : FullDataAfterHook -data class HttpBody(val value: String?, +data class HttpBody(val value: String?, val formData: Map>?, val jsonObject: Any?, - val lazyJsonObject: ((RequestData) -> Any)?) + val lazyJsonObject: ((RequestData) -> Any)?) interface ReplaceableValue { fun get(data: V): T } -fun String.asReplaceableValue() = object : ReplaceableValue { - override fun get(data: RequestData): String { +@Deprecated("Please use accept in request data") +fun String.asReplaceableValue() = object : ReplaceableValue>> { + override fun get(data: RequestData>): String { val replacements = data.getReplacements() var copy = "" + this@asReplaceableValue - replacements.forEach { key, value -> copy = copy.replace(key, value) } + replacements.forEach { (key, value) -> copy = copy.replace(key, value) } return copy } } @@ -62,24 +66,24 @@ fun String.asReplaceableValue() = object : ReplaceableValue interface BeforeHook fun (() -> Unit).toPreHook() = object : SimpleBeforeHook { - override fun accept(req: HttpRequest, data: RequestData) { + override fun accept(req: HttpRequest, data: RequestData) { this@toPreHook() } } interface SessionPersistingBeforeHook : BeforeHook { - suspend fun accept(sessionManager: SessionManager, + suspend fun accept(sessionManager: SessionManager, cookieJar: CookieJar, req: HttpRequest, - data: RequestData) + data: RequestData) } -interface SkipBeforeHook : BeforeHook { - fun skip(requestData: RequestData): Boolean +interface SkipBeforeHook : BeforeHook { + fun skip(requestData: RequestData): Boolean } interface SimpleBeforeHook : BeforeHook { - fun accept(req: HttpRequest, data: RequestData) + fun accept(req: HttpRequest, data: RequestData) } class Envelope(private val message: T, private val maxRetries: Int = 3) { @@ -115,8 +119,8 @@ interface ChainReceivingResponseHook : AfterHook { fun accept(resp: HttpResponse) } -interface FullDataAfterHook : AfterHook { - fun accept(req: HttpRequest, resp: HttpResponse, data: RequestData) +interface FullDataAfterHook: AfterHook { + fun accept(req: HttpRequest, resp: HttpResponse, data: RequestData) } sealed class HttpResult diff --git a/api/src/main/kotlin/net/devslash/FetchDsl.kt b/api/src/main/kotlin/net/devslash/FetchDsl.kt index f782c6e..e82bc48 100644 --- a/api/src/main/kotlin/net/devslash/FetchDsl.kt +++ b/api/src/main/kotlin/net/devslash/FetchDsl.kt @@ -25,13 +25,14 @@ class UnaryAddBuilder { data class RateLimitOptions(val enabled: Boolean, val count: Int, val duration: Duration) @FetchDSL -open class CallBuilder(private val url: String) { +open class CallBuilder(private val url: String) { + private var cookieJar: String? = null - var data: RequestDataSupplier? = null - var body: HttpBody? = null + var data: RequestDataSupplier? = null + var body: HttpBody? = null var type: HttpMethod = HttpMethod.GET var headers: Map>? = null - var onError: OnError? = RetryOnTransitiveError() + var onError: OnError? = RetryOnTransitiveError() private var preHooksList = mutableListOf() private var postHooksList = mutableListOf() @@ -44,8 +45,8 @@ open class CallBuilder(private val url: String) { postHooksList.addAll(UnaryAddBuilder().apply(block).build()) } - fun body(block: BodyBuilder.() -> Unit) { - body = BodyBuilder().apply(block).build() + fun body(block: BodyBuilder.() -> Unit) { + body = BodyBuilder().apply(block).build() } private fun mapHeaders(m: Map>?): Map>? { @@ -62,7 +63,7 @@ open class CallBuilder(private val url: String) { } } - fun build(): Call { + fun build(): Call { val localHeaders = headers if (localHeaders == null || !localHeaders.contains("User-Agent")) { val set = mutableMapOf>() @@ -72,27 +73,29 @@ open class CallBuilder(private val url: String) { set["User-Agent"] = listOf("FetchDSL (Apache-HttpAsyncClient + Kotlin, version not set)") headers = set } - return Call(url, mapHeaders(headers), cookieJar, type, data, body, - onError, preHooksList, postHooksList) + return Call( + url, mapHeaders(headers), cookieJar, type, data, body, + onError, preHooksList, postHooksList + ) } } @FetchDSL -class BodyBuilder { +class BodyBuilder { var value: String? = null var formParams: Map>? = null var jsonObject: Any? = null - var lazyJsonObject: ((RequestData) -> Any)? = null + var lazyJsonObject: ((RequestData) -> Any)? = null - fun build(): HttpBody = HttpBody(value, formParams, jsonObject, lazyJsonObject) + fun build(): HttpBody = HttpBody(value, formParams, jsonObject, lazyJsonObject) } @FetchDSL class MultiCallBuilder { - private var calls = mutableListOf() + private var calls = mutableListOf>() - fun call(url: String, block: CallBuilder.() -> Unit = {}) { - calls.add(CallBuilder(url).apply(block).build()) + fun call(url: String, block: CallBuilder<*>.() -> Unit = {}) { + calls.add(CallBuilder(url).apply(block).build()) } fun calls() = calls @@ -100,8 +103,8 @@ class MultiCallBuilder { @FetchDSL class SessionBuilder { - private var calls = mutableListOf() - private val chained = mutableListOf>() + private var calls = mutableListOf>() + private val chained = mutableListOf>>() var concurrency = 20 var delay: Long? = null @@ -113,18 +116,14 @@ class SessionBuilder { rateOptions = RateLimitOptions(true, count, duration) } - fun call(url: String, block: CallBuilder.() -> Unit = {}) { - calls.add(CallBuilder(url).apply(block).build()) + @JvmName("nonStringCall") + fun call(url: String, block: CallBuilder.() -> Unit = {}) { + calls.add(CallBuilder(url).apply(block).build()) } - // TODO: Re-enable when chaining is stable -// fun chained(block: MultiCallBuilder.(prev: Previous?) -> Unit = {}) { -// if (chained.isNotEmpty()) { -// val line = chained.last().line -// -// } -// chained.add(MultiCallBuilder().apply(block).calls()) -// } + fun call(url: String, block: CallBuilder>.() -> Unit = {}) { + calls.add(CallBuilder>(url).apply(block).build()) + } fun build(): Session = Session(calls, concurrency, delay, rateOptions) } diff --git a/api/src/main/kotlin/net/devslash/SessionManager.kt b/api/src/main/kotlin/net/devslash/SessionManager.kt index 4459b65..fcaf5f6 100644 --- a/api/src/main/kotlin/net/devslash/SessionManager.kt +++ b/api/src/main/kotlin/net/devslash/SessionManager.kt @@ -1,6 +1,6 @@ package net.devslash interface SessionManager { - fun call(call: Call, jar: CookieJar) - fun call(call: Call) + fun call(call: Call, jar: CookieJar) + fun call(call: Call) } diff --git a/api/src/main/kotlin/net/devslash/err/RetryOnTransitiveError.kt b/api/src/main/kotlin/net/devslash/err/RetryOnTransitiveError.kt index d895e86..9e55342 100644 --- a/api/src/main/kotlin/net/devslash/err/RetryOnTransitiveError.kt +++ b/api/src/main/kotlin/net/devslash/err/RetryOnTransitiveError.kt @@ -7,9 +7,9 @@ import net.devslash.HttpRequest import net.devslash.RequestData import java.net.SocketTimeoutException -class RetryOnTransitiveError : ChannelReceiving> { - override suspend fun accept(channel: Channel>>, - envelope: Envelope>, +class RetryOnTransitiveError : ChannelReceiving>> { + override suspend fun accept(channel: Channel>>>, + envelope: Envelope>>, e: Exception) { if (!envelope.shouldProceed()) { // fail after a few failures diff --git a/benchmarks/src/jmh/kotlin/net/devslash/Bencho.kt b/benchmarks/src/jmh/kotlin/net/devslash/Bencho.kt index 3f08325..e2c3693 100644 --- a/benchmarks/src/jmh/kotlin/net/devslash/Bencho.kt +++ b/benchmarks/src/jmh/kotlin/net/devslash/Bencho.kt @@ -1,6 +1,5 @@ package net.devslash -import net.devslash.data.ListDataSupplier import org.openjdk.jmh.annotations.* import java.util.concurrent.TimeUnit diff --git a/build.gradle.kts b/build.gradle.kts index 651882d..2eafa78 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -17,7 +17,7 @@ buildscript { plugins { base `maven-publish` - kotlin("jvm") version "1.3.50" apply false + kotlin("jvm") version "1.4.20" apply false id("com.jfrog.bintray") version "1.8.4" apply false } repositories { @@ -51,13 +51,6 @@ subprojects { } } -// tasks.withType(Test::class).configureEach { -// useJUnitPlatform() -// testLogging { -// events("passed", "skipped", "failed") -// } -// } - configure { user = project.findProperty("bintrayUser") as String? ?: System.getenv("BINTRAY_USER") key = project.findProperty("bintrayApiKey") as String? ?: System.getenv("BINTRAY_API_KEY") diff --git a/examples/src/main/kotlin/net/devslash/examples/PipeExample.kt b/examples/src/main/kotlin/net/devslash/examples/PipeExample.kt index e053235..e27f953 100644 --- a/examples/src/main/kotlin/net/devslash/examples/PipeExample.kt +++ b/examples/src/main/kotlin/net/devslash/examples/PipeExample.kt @@ -1,20 +1,18 @@ package net.devslash.examples -import io.ktor.application.call -import io.ktor.response.respondText -import io.ktor.routing.get -import io.ktor.routing.routing -import io.ktor.server.engine.embeddedServer -import io.ktor.server.netty.Netty -import net.devslash.data.FileDataSupplier -import net.devslash.outputs.WriteFile +import io.ktor.application.* +import io.ktor.response.* +import io.ktor.routing.* +import io.ktor.server.engine.* +import io.ktor.server.netty.* +import net.devslash.data.ListDataSupplier import net.devslash.pipes.ResettablePipe import net.devslash.runHttp import java.net.ServerSocket import java.util.concurrent.TimeUnit fun main() { - val pipe = ResettablePipe({ r, _ -> listOf(String(r.body)) }) + val pipe = ResettablePipe { r, _ -> listOf(String(r.body)) } val port = ServerSocket(0).use { it.localPort } val server = embeddedServer(Netty, port) { routing { @@ -27,10 +25,11 @@ fun main() { val address = "http://localhost:$port" runHttp { call(address) { - data = FileDataSupplier(this.javaClass.getResource("/in.log").path) + // Ok this asserting that it passesa round List types as its big object. therefore this should + data = ListDataSupplier(listOf("1")) after { +pipe - +WriteFile("!1!") +// +WriteFileeFile("!1!") } } call(address) { diff --git a/extensions/src/main/kotlin/net/devslash/data/FileDataSupplier.kt b/extensions/src/main/kotlin/net/devslash/data/FileDataSupplier.kt index 49c1cd5..1717108 100644 --- a/extensions/src/main/kotlin/net/devslash/data/FileDataSupplier.kt +++ b/extensions/src/main/kotlin/net/devslash/data/FileDataSupplier.kt @@ -6,11 +6,11 @@ import net.devslash.RequestDataSupplier import java.io.File import java.util.concurrent.atomic.AtomicInteger -class FileDataSupplier(val name: String, private val split: String = " ") : RequestDataSupplier { +class FileDataSupplier(val name: String, private val split: String = " ") : RequestDataSupplier> { private val sourceFile = File(name).readLines() private val line = AtomicInteger(0) - override suspend fun getDataForRequest(): RequestData? { + override suspend fun getDataForRequest(): RequestData>? { val ourLine = sourceFile.getOrNull(line.getAndIncrement())?.split(split) return if (ourLine == null) null else ListBasedRequestData(ourLine) } diff --git a/extensions/src/main/kotlin/net/devslash/data/ListDataSupplier.kt b/extensions/src/main/kotlin/net/devslash/data/ListDataSupplier.kt index 31074b9..0a15342 100644 --- a/extensions/src/main/kotlin/net/devslash/data/ListDataSupplier.kt +++ b/extensions/src/main/kotlin/net/devslash/data/ListDataSupplier.kt @@ -1,31 +1,46 @@ package net.devslash.data -import net.devslash.ListBasedRequestData +import net.devslash.GenericRequestData import net.devslash.RequestData import net.devslash.RequestDataSupplier import java.util.concurrent.atomic.AtomicInteger -class ListDataSupplier : RequestDataSupplier { +// This aims to create multiple request data pieces. Therefore out can be any type +class ListDataSupplier> : RequestDataSupplier { private val line = AtomicInteger(0) - private val list: Lazy> - private val transform: (T) -> List + private val list: Lazy + private val transform: (InType) -> Out - constructor(list: List, - transform: (T) -> List = { listOf("$it") }) { + companion object { + @JvmName("invoke1") + operator fun invoke(list: List): ListDataSupplier, String, List> { + return ListDataSupplier(list, { listOf(it) }) + } + + operator fun invoke(list: List): ListDataSupplier> { + return ListDataSupplier(list, {it}) + } + } + + constructor( + list: L, + transform: (InType) -> Out + ) { this.list = lazy { list } this.transform = transform - } - constructor(list: Lazy>, - transform: (T) -> List = { listOf("$it") }) { + constructor( + list: Lazy, + transform: (InType) -> Out + ) { this.list = list this.transform = transform } - override suspend fun getDataForRequest(): RequestData? { + override suspend fun getDataForRequest(): RequestData? { val index = line.getAndIncrement() val obj = list.value.getOrNull(index) ?: return null - return ListBasedRequestData(transform(obj)) + return GenericRequestData(transform(obj)) } } diff --git a/extensions/src/main/kotlin/net/devslash/data/ModifiableSupplier.kt b/extensions/src/main/kotlin/net/devslash/data/ModifiableSupplier.kt index b19d98b..d645da2 100644 --- a/extensions/src/main/kotlin/net/devslash/data/ModifiableSupplier.kt +++ b/extensions/src/main/kotlin/net/devslash/data/ModifiableSupplier.kt @@ -17,16 +17,16 @@ import java.util.concurrent.atomic.AtomicInteger * During this time between the last from the delegate, and the final request going through the * [getDataForRequest] method will block the calling thread as it's unsure of its response. */ -class ModifiableSupplier(private val delegate: RequestDataSupplier) : RequestDataSupplier, SimpleAfterHook { +class ModifiableSupplier(private val delegate: RequestDataSupplier) : RequestDataSupplier, SimpleAfterHook { - private val modifiedQueue: ConcurrentLinkedQueue = ConcurrentLinkedQueue() + private val modifiedQueue: ConcurrentLinkedQueue> = ConcurrentLinkedQueue() private val sentRequests = AtomicInteger(0) private val receivedResponses = AtomicInteger(0) - fun add(data: RequestData) = modifiedQueue.add(data) + fun add(data: RequestData) = modifiedQueue.add(data) - override suspend fun getDataForRequest(): RequestData? { + override suspend fun getDataForRequest(): RequestData? { val data = delegate.getDataForRequest() if (data != null) { // we'll send a request, thus count. @@ -45,7 +45,7 @@ class ModifiableSupplier(private val delegate: RequestDataSupplier) : RequestDat /** * In this case, there was no obvious candidate. Thus we want to wait until a request comes */ - private suspend fun waitOrNull(): RequestData? { + private suspend fun waitOrNull(): RequestData? { while (sentRequests.get() > receivedResponses.get()) { // Attempt to find one, if you don't get it then block until there's some output returned val result = modifiedQueue.poll() diff --git a/extensions/src/main/kotlin/net/devslash/err/DeadLetterQueue.kt b/extensions/src/main/kotlin/net/devslash/err/DeadLetterQueue.kt index 3614d7a..fce2ecc 100644 --- a/extensions/src/main/kotlin/net/devslash/err/DeadLetterQueue.kt +++ b/extensions/src/main/kotlin/net/devslash/err/DeadLetterQueue.kt @@ -8,15 +8,15 @@ import net.devslash.RequestData import java.io.File class DeadLetterQueue(filename: String, - private val split: String = " ") : ChannelReceiving> { + private val split: String = " ") : ChannelReceiving>> { private val file = File(filename) init { file.createNewFile() } - override suspend fun accept(channel: Channel>>, - envelope: Envelope>, + override suspend fun accept(channel: Channel>>>, + envelope: Envelope>>, e: Exception) { val data = envelope.get().second.getReplacements() var builder = "" diff --git a/extensions/src/main/kotlin/net/devslash/outputs/AppendFile.kt b/extensions/src/main/kotlin/net/devslash/outputs/AppendFile.kt index 530211d..6a8dda1 100644 --- a/extensions/src/main/kotlin/net/devslash/outputs/AppendFile.kt +++ b/extensions/src/main/kotlin/net/devslash/outputs/AppendFile.kt @@ -5,24 +5,29 @@ import java.io.BufferedOutputStream import java.io.FileOutputStream import java.io.OutputStream -class AppendFile(private val fileName: String, - private val out: OutputFormat = DefaultOutput()) : BasicOutput { +class AppendFile( + private val fileName: String, + private val out: OutputFormat = DefaultOutput() +) : BasicOutput { private val lock = Object() private var memoizedFile: OutputStream? = null init { - if (fileName.contains(Regex("!\\d+!"))) { + memoizedFile = if (fileName.contains(Regex("!\\d+!"))) { // then it's non memoizable - memoizedFile = null + null } else { - memoizedFile = BufferedOutputStream(FileOutputStream(fileName, true)) + BufferedOutputStream(FileOutputStream(fileName, true)) } } - override fun accept(req: HttpRequest, resp: HttpResponse, data: RequestData) { - val f = memoizedFile ?: BufferedOutputStream(FileOutputStream(fileName.asReplaceableValue().get( - data), true)) + override fun accept(req: HttpRequest, resp: HttpResponse, data: RequestData) { + val f = memoizedFile ?: BufferedOutputStream( + FileOutputStream( + data.accept(fileName), true + ) + ) val output = out.accept(resp, data) if (output != null) { diff --git a/extensions/src/main/kotlin/net/devslash/outputs/DebugOutput.kt b/extensions/src/main/kotlin/net/devslash/outputs/DebugOutput.kt index cc1b9ca..58ad63f 100644 --- a/extensions/src/main/kotlin/net/devslash/outputs/DebugOutput.kt +++ b/extensions/src/main/kotlin/net/devslash/outputs/DebugOutput.kt @@ -5,7 +5,7 @@ import net.devslash.OutputFormat import net.devslash.RequestData class DebugOutput : OutputFormat { - override fun accept(resp: HttpResponse, rep: RequestData): ByteArray? { + override fun accept(resp: HttpResponse, rep: RequestData): ByteArray? { return """ ---------------- url: ${resp.url} diff --git a/extensions/src/main/kotlin/net/devslash/outputs/DefaultOutput.kt b/extensions/src/main/kotlin/net/devslash/outputs/DefaultOutput.kt index a31101f..73fa3ee 100644 --- a/extensions/src/main/kotlin/net/devslash/outputs/DefaultOutput.kt +++ b/extensions/src/main/kotlin/net/devslash/outputs/DefaultOutput.kt @@ -4,8 +4,8 @@ import net.devslash.HttpResponse import net.devslash.OutputFormat import net.devslash.RequestData -class DefaultOutput : OutputFormat { - override fun accept(resp: HttpResponse, rep: RequestData): ByteArray? { +class DefaultOutput: OutputFormat { + override fun accept(resp: HttpResponse, rep: RequestData): ByteArray { return resp.body } } diff --git a/extensions/src/main/kotlin/net/devslash/outputs/StdOut.kt b/extensions/src/main/kotlin/net/devslash/outputs/StdOut.kt index 379254f..e5e3133 100644 --- a/extensions/src/main/kotlin/net/devslash/outputs/StdOut.kt +++ b/extensions/src/main/kotlin/net/devslash/outputs/StdOut.kt @@ -4,7 +4,7 @@ import net.devslash.* import java.io.PrintStream class StdOut(private val output: PrintStream = System.out, private val format: OutputFormat = DefaultOutput()) : BasicOutput { - override fun accept(req: HttpRequest, resp: HttpResponse, data: RequestData) { + override fun accept(req: HttpRequest, resp: HttpResponse, data: RequestData) { format.accept(resp, data)?.let { output.println(String(it)) } diff --git a/extensions/src/main/kotlin/net/devslash/outputs/WriteFile.kt b/extensions/src/main/kotlin/net/devslash/outputs/WriteFile.kt index e71ee6d..fc826f3 100644 --- a/extensions/src/main/kotlin/net/devslash/outputs/WriteFile.kt +++ b/extensions/src/main/kotlin/net/devslash/outputs/WriteFile.kt @@ -4,13 +4,15 @@ import net.devslash.* import java.io.File -class WriteFile(private val fileName: String, - private val out: OutputFormat = DefaultOutput()) : BasicOutput { +class WriteFile( + private val fileName: String, + private val out: OutputFormat = DefaultOutput() +) : BasicOutput { private val lock = Object() - override fun accept(req: HttpRequest, resp: HttpResponse, data: RequestData) { + override fun accept(req: HttpRequest, resp: HttpResponse, data: RequestData) { synchronized(lock) { - val f = File(fileName.asReplaceableValue().get(data)) + val f = File(data.accept(fileName)) val output = out.accept(resp, data) if (output != null) { f.writeBytes(output) @@ -18,3 +20,4 @@ class WriteFile(private val fileName: String, } } } + diff --git a/extensions/src/main/kotlin/net/devslash/pipes/Pipe.kt b/extensions/src/main/kotlin/net/devslash/pipes/Pipe.kt index 363a6dd..8f1e673 100644 --- a/extensions/src/main/kotlin/net/devslash/pipes/Pipe.kt +++ b/extensions/src/main/kotlin/net/devslash/pipes/Pipe.kt @@ -3,27 +3,35 @@ package net.devslash.pipes import net.devslash.* import java.util.concurrent.ConcurrentLinkedDeque -class Pipe(val acceptor: (HttpResponse, RequestData) -> List, private val split: String? = null) : BasicOutput, RequestDataSupplier { +class Pipe(val acceptor: (HttpResponse, RequestData) -> List, private val split: String? = null) : BasicOutput, RequestDataSupplier { private val storage = ConcurrentLinkedDeque() - override suspend fun getDataForRequest(): RequestData? { + override suspend fun getDataForRequest(): RequestData? { val currentValue = storage.poll() ?: return null val line = if (split != null) { currentValue.split(split) } else listOf(currentValue) - return object : RequestData { + return object : RequestData { override fun getReplacements(): Map { return line.mapIndexed { index, string -> "!" + (index + 1) + "!" to string }.toMap() } + + override fun get(): T { + TODO("Not yet implemented") + } + + override fun accept(v: String): String { + TODO("Not yet implemented") + } } } - override fun accept(req: HttpRequest, resp: HttpResponse, data: RequestData) { - val newResults = acceptor(resp, data) + override fun accept(req: HttpRequest, resp: HttpResponse, data: RequestData) { + val newResults = acceptor(resp, data as RequestData) storage.addAll(newResults) } } diff --git a/extensions/src/main/kotlin/net/devslash/pipes/ResettablePipe.kt b/extensions/src/main/kotlin/net/devslash/pipes/ResettablePipe.kt index 6809d3b..a2011fc 100644 --- a/extensions/src/main/kotlin/net/devslash/pipes/ResettablePipe.kt +++ b/extensions/src/main/kotlin/net/devslash/pipes/ResettablePipe.kt @@ -3,28 +3,24 @@ package net.devslash.pipes import net.devslash.* import java.util.concurrent.atomic.AtomicInteger -class ResettablePipe(val acceptor: (HttpResponse, RequestData) -> List, - private val split: String? = null) : BasicOutput, RequestDataSupplier { +class ResettablePipe( + val acceptor: (HttpResponse, RequestData) -> List +) : BasicOutput, RequestDataSupplier> { private val index = AtomicInteger(0) - private val storage = mutableListOf() + private val storage = mutableListOf() - override suspend fun getDataForRequest(): RequestData? { + override suspend fun getDataForRequest(): RequestData>? { val currentValue = storage.getOrNull(index.getAndIncrement()) ?: return null - - val line = if (split != null) { - currentValue.split(split) - } else listOf(currentValue) - - return ListBasedRequestData(line) + return ListBasedRequestData(listOf(currentValue)) } override fun init() { reset() } - override fun accept(req: HttpRequest, resp: HttpResponse, data: RequestData) { - val newResults = acceptor(resp, data) + override fun accept(req: HttpRequest, resp: HttpResponse, data: RequestData) { + val newResults = acceptor(resp, data as RequestData) storage.addAll(newResults) } diff --git a/extensions/src/main/kotlin/net/devslash/post/Filter.kt b/extensions/src/main/kotlin/net/devslash/post/Filter.kt index 4012176..085300d 100644 --- a/extensions/src/main/kotlin/net/devslash/post/Filter.kt +++ b/extensions/src/main/kotlin/net/devslash/post/Filter.kt @@ -13,7 +13,7 @@ class FilterBuilder { class Filter(private val pred: (HttpResponse) -> Boolean, private val builder: FilterBuilder.() -> Unit) : FullDataAfterHook { - override fun accept(req: HttpRequest, resp: HttpResponse, data: RequestData) { + override fun accept(req: HttpRequest, resp: HttpResponse, data: RequestData) { val current = FilterBuilder().apply(builder) if (pred(resp)) { current.posts.forEach { diff --git a/extensions/src/main/kotlin/net/devslash/post/LogResponse.kt b/extensions/src/main/kotlin/net/devslash/post/LogResponse.kt index 9f07262..bcdf929 100644 --- a/extensions/src/main/kotlin/net/devslash/post/LogResponse.kt +++ b/extensions/src/main/kotlin/net/devslash/post/LogResponse.kt @@ -3,13 +3,13 @@ package net.devslash.post import net.devslash.* class DefaultResponseFormat : OutputFormat { - override fun accept(resp: HttpResponse, rep: RequestData): ByteArray? { + override fun accept(resp: HttpResponse, rep: RequestData): ByteArray? { return "Resp ${resp.url} -> ${resp.statusCode}".toByteArray() } } class LogResponse(private val format: OutputFormat = DefaultResponseFormat()) : FullDataAfterHook { - override fun accept(req: HttpRequest, resp: HttpResponse, data: RequestData) { + override fun accept(req: HttpRequest, resp: HttpResponse, data: RequestData) { format.accept(resp, data)?.let { println(String(it)) } diff --git a/extensions/src/main/kotlin/net/devslash/pre/LogRequest.kt b/extensions/src/main/kotlin/net/devslash/pre/LogRequest.kt index 3f2d3b9..5c58917 100644 --- a/extensions/src/main/kotlin/net/devslash/pre/LogRequest.kt +++ b/extensions/src/main/kotlin/net/devslash/pre/LogRequest.kt @@ -5,7 +5,7 @@ import net.devslash.RequestData import net.devslash.SimpleBeforeHook class LogRequest : SimpleBeforeHook { - override fun accept(req: HttpRequest, data: RequestData) { + override fun accept(req: HttpRequest, data: RequestData) { println("Requesting to ${req.url}") } } diff --git a/extensions/src/main/kotlin/net/devslash/pre/Once.kt b/extensions/src/main/kotlin/net/devslash/pre/Once.kt index 784d350..cae3ab0 100644 --- a/extensions/src/main/kotlin/net/devslash/pre/Once.kt +++ b/extensions/src/main/kotlin/net/devslash/pre/Once.kt @@ -13,10 +13,10 @@ class Once(private val before: BeforeHook) : SessionPersistingBeforeHook { private val flag = AtomicBoolean(false) - override suspend fun accept(sessionManager: SessionManager, + override suspend fun accept(sessionManager: SessionManager, cookieJar: CookieJar, req: HttpRequest, - data: RequestData) { + data: RequestData) { if (flag.compareAndSet(false, true)) { val methods: KClass = before::class diff --git a/extensions/src/main/kotlin/net/devslash/pre/SkipIf.kt b/extensions/src/main/kotlin/net/devslash/pre/SkipIf.kt index bf5b502..e4f3f19 100644 --- a/extensions/src/main/kotlin/net/devslash/pre/SkipIf.kt +++ b/extensions/src/main/kotlin/net/devslash/pre/SkipIf.kt @@ -3,8 +3,8 @@ package net.devslash.pre import net.devslash.RequestData import net.devslash.SkipBeforeHook -class SkipIf(private val predicate: (RequestData) -> Boolean): SkipBeforeHook { - override fun skip(requestData: RequestData): Boolean { +class SkipIf(private val predicate: (RequestData) -> Boolean): SkipBeforeHook { + override fun skip(requestData: RequestData): Boolean { return predicate(requestData) } } diff --git a/extensions/src/test/kotlin/net/devslash/BodyProviderTest.kt b/extensions/src/test/kotlin/net/devslash/BodyProviderTest.kt index 8f642c9..d588c5e 100644 --- a/extensions/src/test/kotlin/net/devslash/BodyProviderTest.kt +++ b/extensions/src/test/kotlin/net/devslash/BodyProviderTest.kt @@ -1,7 +1,6 @@ package net.devslash import net.devslash.util.getCall -import net.devslash.util.requestDataFromList import org.junit.Assert.assertEquals import org.junit.Test @@ -9,7 +8,7 @@ internal class BodyProviderTest { @Test fun testEmpty() { val provider = getBodyProvider( - getCall(), requestDataFromList(listOf("b", "d")) + getCall(), ListBasedRequestData(listOf("b", "d")) ) assertEquals(EmptyBodyProvider::class, provider::class) @@ -18,26 +17,27 @@ internal class BodyProviderTest { @Test fun testWithBody() { val provider = getBodyProvider( - getCall(HttpBody(null, mapOf("a" to listOf("b"), "c" to listOf("d")), null, null)), requestDataFromList() + getCall(HttpBody(null, mapOf("a" to listOf("b"), "c" to listOf("d")), null, null)), ListBasedRequestData( + listOf()) ) - assertEquals(mapOf("a" to listOf("b"), "c" to listOf("d")), (provider as FormBody).get()) + assertEquals(mapOf("a" to listOf("b"), "c" to listOf("d")), (provider as FormBody).get()) } @Test fun testBodyWithReplaceableValues() { val provider = getBodyProvider( - getCall(HttpBody("a=!1!&c=!2!", null, null, null)), requestDataFromList(listOf("b", "d")) + getCall(HttpBody("a=!1!&c=!2!", null, null, null)), ListBasedRequestData(listOf("b", "d")) ) - assertEquals("a=b&c=d", (provider as BasicBodyProvider).get()) + assertEquals("a=b&c=d", (provider as BasicBodyProvider).get()) } @Test fun testParamsWithReplacement() { val provider = getBodyProvider( getCall(HttpBody(null, mapOf("a" to listOf("!1!"), "c" to listOf("!2!")), null, null)), - requestDataFromList(listOf("b", "d")) + ListBasedRequestData(listOf("b", "d")) ) - assertEquals(mapOf("a" to listOf("b"), "c" to listOf("d")), (provider as FormBody).get()) + assertEquals(mapOf("a" to listOf("b"), "c" to listOf("d")), (provider as FormBody).get()) } } diff --git a/extensions/src/test/kotlin/net/devslash/CookieJarTest.kt b/extensions/src/test/kotlin/net/devslash/CookieJarTest.kt index ebd39e8..39a85a4 100644 --- a/extensions/src/test/kotlin/net/devslash/CookieJarTest.kt +++ b/extensions/src/test/kotlin/net/devslash/CookieJarTest.kt @@ -1,6 +1,5 @@ package net.devslash -import net.devslash.util.requestDataFromList import org.hamcrest.CoreMatchers.`is` import org.hamcrest.CoreMatchers.nullValue import org.hamcrest.MatcherAssert.assertThat @@ -16,7 +15,7 @@ internal class CookieJarTest { @Test fun testSingleCookieSet() { jar.accept(responseWithHeaders(mapOf("Set-Cookie" to listOf("A=B")))) - jar.accept(standardRequest, requestDataFromList(listOf())) + jar.accept(standardRequest, ListBasedRequestData(listOf())) assertThat(standardRequest.headers["Cookie"], equalTo(listOf("A=B"))) } @@ -25,7 +24,7 @@ internal class CookieJarTest { fun testMultipleCaseCookieSet() { jar.accept( responseWithHeaders(mapOf("set-Cookie" to listOf("A=B"), "SET-COOKIE" to listOf("C=D")))) - jar.accept(standardRequest, requestDataFromList(listOf())) + jar.accept(standardRequest, ListBasedRequestData(listOf())) assertThat(standardRequest.headers["Cookie"], equalTo(listOf("A=B; C=D"))) @@ -34,7 +33,7 @@ internal class CookieJarTest { @Test fun testSetMultipleOfTheSameKey() { jar.accept( responseWithHeaders(mapOf("set-Cookie" to listOf("A=B"), "SET-COOKIE" to listOf("A=D")))) - jar.accept(standardRequest, requestDataFromList(listOf())) + jar.accept(standardRequest, ListBasedRequestData(listOf())) assertThat(standardRequest.headers["Cookie"], equalTo(listOf("A=D"))) @@ -45,8 +44,8 @@ internal class CookieJarTest { jar.accept(responseWithHeaders(mapOf("Set-Cookie" to listOf("A=B")))) val otherSizeRequest = HttpRequest(HttpMethod.GET, "https://differentDomain.com", EmptyBodyProvider) - jar.accept(otherSizeRequest, requestDataFromList(listOf())) - jar.accept(standardRequest, requestDataFromList(listOf())) + jar.accept(otherSizeRequest, ListBasedRequestData(listOf())) + jar.accept(standardRequest, ListBasedRequestData(listOf())) assertThat(otherSizeRequest.headers["Cookie"], `is`(nullValue())) assertThat(standardRequest.headers["Cookie"], equalTo(listOf("A=B"))) @@ -57,8 +56,8 @@ internal class CookieJarTest { jar.accept(responseWithHeaders(mapOf("Set-Cookie" to listOf("A=B")))) val httpRequest = HttpRequest(HttpMethod.GET, "http://example.com", EmptyBodyProvider) - jar.accept(httpRequest, requestDataFromList(listOf())) - jar.accept(standardRequest, requestDataFromList(listOf("A=B"))) + jar.accept(httpRequest, ListBasedRequestData(listOf())) + jar.accept(standardRequest, ListBasedRequestData(listOf("A=B"))) } private fun responseWithHeaders(headers: Map>, diff --git a/extensions/src/test/kotlin/net/devslash/HttpSessionManagerTest.kt b/extensions/src/test/kotlin/net/devslash/HttpSessionManagerTest.kt index 2c806ca..83c23c0 100644 --- a/extensions/src/test/kotlin/net/devslash/HttpSessionManagerTest.kt +++ b/extensions/src/test/kotlin/net/devslash/HttpSessionManagerTest.kt @@ -47,7 +47,7 @@ internal class HttpSessionManagerTest : ServerTest() { call(address) { after { +object : BasicOutput { - override fun accept(req: HttpRequest, resp: HttpResponse, data: RequestData) { + override fun accept(req: HttpRequest, resp: HttpResponse, data: RequestData) { cookie = resp.headers["set-cookie"]!![0] body = String(resp.body) } diff --git a/extensions/src/test/kotlin/net/devslash/it/HttpBounceTest.kt b/extensions/src/test/kotlin/net/devslash/it/HttpBounceTest.kt index ccf5f86..47c676c 100644 --- a/extensions/src/test/kotlin/net/devslash/it/HttpBounceTest.kt +++ b/extensions/src/test/kotlin/net/devslash/it/HttpBounceTest.kt @@ -1,15 +1,17 @@ package net.devslash.it -import io.ktor.application.call -import io.ktor.http.Headers -import io.ktor.request.receiveText -import io.ktor.response.respondText -import io.ktor.routing.get -import io.ktor.routing.post -import io.ktor.routing.routing -import io.ktor.server.engine.ApplicationEngine +import io.ktor.application.* +import io.ktor.http.* +import io.ktor.request.* +import io.ktor.response.* +import io.ktor.routing.* +import io.ktor.server.engine.* import kotlinx.coroutines.runBlocking import net.devslash.* +import net.devslash.HttpMethod +import net.devslash.data.Capture +import net.devslash.data.ListDataSupplier +import net.devslash.post.LogResponse import net.devslash.pre.SkipIf import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse @@ -43,6 +45,27 @@ class HttpBounceTest : ServerTest() { assertEquals("RESULT", bodyResult) } + @Test + fun testDataClassBasedCall() { + data class TClass(@Capture("!2!") val i: Int, val a: Boolean) + runWith { + routing { + get("/2") { + call.respond(HttpStatusCode.OK, "Nice") + } + } + } + + runHttp { + call("$address/!2!") { + data = ListDataSupplier(listOf(TClass(2, true))) {it} + after { + +LogResponse() + } + } + } + } + @Test fun testWithBodyParams() { var sentBody = "" @@ -82,8 +105,9 @@ class HttpBounceTest : ServerTest() { } runHttp { - call(address) { - this.headers = mapOf("A" to listOf(ProvidedValue { r -> "!1!".asReplaceableValue().get(r) + "."}), "C" to listOf("D")) + call>(address) { + this.headers = + mapOf("A" to listOf(ProvidedValue> { it.get()["!1!"]!! + "." }), "C" to listOf("D")) data = SingleUseDataSupplier(mapOf("!1!" to "Hi")) } } @@ -106,7 +130,7 @@ class HttpBounceTest : ServerTest() { runHttp { call(address) { before { - +SkipIf { true } + +SkipIf { true } } } } diff --git a/extensions/src/test/kotlin/net/devslash/outputs/AppendFileTest.kt b/extensions/src/test/kotlin/net/devslash/outputs/AppendFileTest.kt index 0a20190..dc116a6 100644 --- a/extensions/src/test/kotlin/net/devslash/outputs/AppendFileTest.kt +++ b/extensions/src/test/kotlin/net/devslash/outputs/AppendFileTest.kt @@ -14,7 +14,7 @@ internal class AppendFileTest { @Rule @JvmField - public val tmpDir = TemporaryFolder() + val tmpDir = TemporaryFolder() @Test fun testSimpleAppendTest() { @@ -23,10 +23,10 @@ internal class AppendFileTest { appender.accept(getBasicRequest(), getResponseWithBody("abc".toByteArray()), - ListBasedRequestData()) + ListBasedRequestData(listOf())) appender.accept(getBasicRequest(), getResponseWithBody("def".toByteArray()), - ListBasedRequestData()) + ListBasedRequestData(listOf())) assertThat(file.readText(), equalTo("abc\ndef\n")) } diff --git a/extensions/src/test/kotlin/net/devslash/pipes/PipeTest.kt b/extensions/src/test/kotlin/net/devslash/pipes/PipeTest.kt index 7e309b8..c3e6242 100644 --- a/extensions/src/test/kotlin/net/devslash/pipes/PipeTest.kt +++ b/extensions/src/test/kotlin/net/devslash/pipes/PipeTest.kt @@ -2,8 +2,8 @@ package net.devslash.pipes import kotlinx.coroutines.runBlocking import net.devslash.HttpResponse +import net.devslash.ListBasedRequestData import net.devslash.util.getBasicRequest -import net.devslash.util.requestDataFromList import org.hamcrest.CoreMatchers.* import org.hamcrest.MatcherAssert.assertThat import org.junit.Test @@ -13,19 +13,19 @@ internal class PipeTest { @Test fun testPipeStartsEmpty() = runBlocking { - val pipe = Pipe({ _, _ -> listOf("A", "B") }, null) + val pipe = Pipe({ _, _ -> listOf("A", "B") }, null) assertThat(pipe.getDataForRequest(), nullValue()) } @Test fun testPipeSingleCase() = runBlocking { - val pipe = Pipe({ r, _ -> listOf(String(r.body)) }, null) + val pipe = Pipe({ r, _ -> listOf(String(r.body)) }, null) pipe.accept( getBasicRequest(), HttpResponse(URL("http://"), 200, mapOf(), "result".toByteArray()), - requestDataFromList(listOf()) + ListBasedRequestData(listOf()) ) val data = pipe.getDataForRequest()!! @@ -36,11 +36,11 @@ internal class PipeTest { @Test fun testPipeSplitsCorrectly() = runBlocking { - val pipe = Pipe({ _, _ -> listOf("a b c") }, " ") + val pipe = Pipe({ _, _ -> listOf("a b c") }, " ") pipe.accept( getBasicRequest(), HttpResponse(URL("http://"), 200, mapOf(), byteArrayOf()), - requestDataFromList(listOf()) + ListBasedRequestData(listOf()) ) val data = pipe.getDataForRequest()!! @@ -51,11 +51,11 @@ internal class PipeTest { @Test fun testPipeCanReturnMultipleResults() = runBlocking { val vals = listOf("a", "b", "c") - val pipe = Pipe({ _, _ -> vals }, " ") + val pipe = Pipe({ _, _ -> vals }, " ") pipe.accept( getBasicRequest(), HttpResponse(URL("http://"), 200, mapOf(), byteArrayOf()), - requestDataFromList(listOf()) + ListBasedRequestData(listOf()) ) vals.forEach { @@ -65,21 +65,21 @@ internal class PipeTest { @Test fun testPipeAcceptsMultipleAndReturnsInOrder() = runBlocking { - val pipe = Pipe({ r, _ -> listOf(String(r.body)) }, " ") + val pipe = Pipe({ r, _ -> listOf(String(r.body)) }, " ") pipe.accept( getBasicRequest(), HttpResponse(URL("http://"), 200, mapOf(), "a".toByteArray()), - requestDataFromList(listOf()) + ListBasedRequestData(listOf()) ) pipe.accept( getBasicRequest(), HttpResponse(URL("http://"), 200, mapOf(), "b".toByteArray()), - requestDataFromList(listOf()) + ListBasedRequestData(listOf()) ) pipe.accept( getBasicRequest(), HttpResponse(URL("http://"), 200, mapOf(), "c".toByteArray()), - requestDataFromList(listOf()) + ListBasedRequestData(listOf()) ) val values = listOf("a", "b", "c") diff --git a/extensions/src/test/kotlin/net/devslash/pipes/ResettablePipeTest.kt b/extensions/src/test/kotlin/net/devslash/pipes/ResettablePipeTest.kt index 79143ab..3fc1ef5 100644 --- a/extensions/src/test/kotlin/net/devslash/pipes/ResettablePipeTest.kt +++ b/extensions/src/test/kotlin/net/devslash/pipes/ResettablePipeTest.kt @@ -2,8 +2,8 @@ package net.devslash.pipes import kotlinx.coroutines.runBlocking import net.devslash.HttpResponse +import net.devslash.ListBasedRequestData import net.devslash.util.getBasicRequest -import net.devslash.util.requestDataFromList import org.hamcrest.CoreMatchers.* import org.hamcrest.MatcherAssert.assertThat import org.junit.Test @@ -13,19 +13,19 @@ internal class ResettablePipeTest { @Test fun testPipeStartsEmpty() = runBlocking{ - val pipe = ResettablePipe({ _, _ -> listOf("A", "B") }, null) + val pipe = ResettablePipe { _, _ -> listOf("A", "B") } assertThat(pipe.getDataForRequest(), nullValue()) } @Test fun testPipeSingleCase() = runBlocking { - val pipe = ResettablePipe({ r, _ -> listOf(String(r.body)) }, null) + val pipe = ResettablePipe({ r, _ -> listOf(String(r.body)) }) pipe.accept( getBasicRequest(), HttpResponse(URL("http://"), 200, mapOf(), "result".toByteArray()), - requestDataFromList(listOf()) + ListBasedRequestData(listOf()) ) val data = pipe.getDataForRequest()!! @@ -36,17 +36,17 @@ internal class ResettablePipeTest { // now reset pipe.reset() val nextData = pipe.getDataForRequest()!! - assertThat(data, equalTo(nextData)) + assertThat(data.get(), equalTo(nextData.get())) } @Test fun testPipeCanReturnMultipleResults() = runBlocking { val vals = listOf("a", "b", "c") - val pipe = Pipe({ _, _ -> vals }, " ") + val pipe = Pipe({ _, _ -> vals }, " ") pipe.accept( getBasicRequest(), HttpResponse(URL("http://"), 200, mapOf(), byteArrayOf()), - requestDataFromList(listOf()) + ListBasedRequestData(listOf()) ) vals.forEach { @@ -56,21 +56,21 @@ internal class ResettablePipeTest { @Test fun testPipeAcceptsMultipleAndReturnsInOrder() = runBlocking { - val pipe = Pipe({ r, _ -> listOf(String(r.body)) }, " ") + val pipe = Pipe({ r, _ -> listOf(String(r.body)) }, " ") pipe.accept( getBasicRequest(), HttpResponse(URL("http://"), 200, mapOf(), "a".toByteArray()), - requestDataFromList(listOf()) + ListBasedRequestData(listOf()) ) pipe.accept( getBasicRequest(), HttpResponse(URL("http://"), 200, mapOf(), "b".toByteArray()), - requestDataFromList(listOf()) + ListBasedRequestData(listOf()) ) pipe.accept( getBasicRequest(), HttpResponse(URL("http://"), 200, mapOf(), "c".toByteArray()), - requestDataFromList(listOf()) + ListBasedRequestData(listOf()) ) val values = listOf("a", "b", "c") diff --git a/extensions/src/test/kotlin/net/devslash/pre/OnceTest.kt b/extensions/src/test/kotlin/net/devslash/pre/OnceTest.kt index d121788..e65a383 100644 --- a/extensions/src/test/kotlin/net/devslash/pre/OnceTest.kt +++ b/extensions/src/test/kotlin/net/devslash/pre/OnceTest.kt @@ -1,23 +1,24 @@ package net.devslash.pre +import io.mockk.mockk import kotlinx.coroutines.runBlocking import net.devslash.* import net.devslash.util.getBasicRequest import net.devslash.util.getCookieJar -import net.devslash.util.getSessionManager -import net.devslash.util.requestDataFromList import org.junit.Assert.assertEquals import org.junit.Assert.fail import org.junit.Test internal class OnceTest { + private val sManager = mockk() + @Test fun testLambda() = runBlocking { var count = 0 val o = Once({ count++; Unit }.toPreHook()) - o.accept(getSessionManager(), getCookieJar(), getBasicRequest(), requestDataFromList(listOf())) + o.accept(sManager, getCookieJar(), getBasicRequest(), ListBasedRequestData(listOf())) assertEquals(1, count) } @@ -33,8 +34,8 @@ internal class OnceTest { } }) - o.accept(getSessionManager(), getCookieJar(), getBasicRequest(), requestDataFromList(listOf())) - o.accept(getSessionManager(), getCookieJar(), getBasicRequest(), requestDataFromList(listOf())) + o.accept(sManager, getCookieJar(), getBasicRequest(), ListBasedRequestData(listOf())) + o.accept(sManager, getCookieJar(), getBasicRequest(), ListBasedRequestData(listOf())) assertEquals(1, count) } @@ -50,10 +51,10 @@ internal class OnceTest { }) try { - o.accept(getSessionManager(), + o.accept(sManager, getCookieJar(), getBasicRequest(), - requestDataFromList(listOf())) + ListBasedRequestData(listOf())) fail("Should have an exception") } catch (e: InvalidHookException) { // ignore @@ -65,15 +66,17 @@ internal class OnceTest { fun testWorksWithComplexHook() = runBlocking { var count = 0 val o = Once(object : SessionPersistingBeforeHook { - override suspend fun accept(sessionManager: SessionManager, - cookieJar: CookieJar, - req: HttpRequest, - data: RequestData) { + override suspend fun accept( + sessionManager: SessionManager, + cookieJar: CookieJar, + req: HttpRequest, + data: RequestData + ) { count++ } }) - o.accept(getSessionManager(), getCookieJar(), getBasicRequest(), requestDataFromList(listOf())) + o.accept(sManager, getCookieJar(), getBasicRequest(), ListBasedRequestData(listOf())) assertEquals(1, count) } diff --git a/extensions/src/test/kotlin/net/devslash/pre/SkipIfTest.kt b/extensions/src/test/kotlin/net/devslash/pre/SkipIfTest.kt index 37a9718..37b4e54 100644 --- a/extensions/src/test/kotlin/net/devslash/pre/SkipIfTest.kt +++ b/extensions/src/test/kotlin/net/devslash/pre/SkipIfTest.kt @@ -8,7 +8,7 @@ internal class SkipIfTest { @Test fun testSkipIf() { - val skip = SkipIf { true } + val skip = SkipIf> { true } val result = skip.skip(ListBasedRequestData(listOf())) assertTrue(result) } diff --git a/extensions/src/test/kotlin/net/devslash/util/TestUtils.kt b/extensions/src/test/kotlin/net/devslash/util/TestUtils.kt index 75f4c44..7533553 100644 --- a/extensions/src/test/kotlin/net/devslash/util/TestUtils.kt +++ b/extensions/src/test/kotlin/net/devslash/util/TestUtils.kt @@ -1,23 +1,8 @@ package net.devslash.util -import io.ktor.client.engine.apache.Apache import net.devslash.* import java.net.URL -fun requestDataFromList(listOf: List? = null): RequestData { - return object : RequestData { - override fun getReplacements(): Map { - if (listOf != null) { - return listOf.mapIndexed { i, p -> - "!${i + 1}!" to p - }.toMap() - } - - return mapOf() - } - } -} - fun getBasicRequest() : HttpRequest { return HttpRequest(HttpMethod.GET, "https://example.com", EmptyBodyProvider) } @@ -26,14 +11,6 @@ fun getCookieJar(): CookieJar { return CookieJar() } -fun getSessionManager(): SessionManager { - return HttpSessionManager(HttpDriver(ConfigBuilder().build()), getSession()) -} - -fun getSession(): Session { - return SessionBuilder().build() -} - fun getResponseWithBody(body: ByteArray) : HttpResponse { return HttpResponse(URL("http://example.com"), 200, mapOf(), body) } @@ -42,7 +19,7 @@ fun getResponse(): HttpResponse { return getResponseWithBody("Body".toByteArray()) } -fun getCall(sup: HttpBody? = null, url: String = "https://example.com") = CallBuilder( +fun getCall(sup: HttpBody? = null, url: String = "https://example.com") = CallBuilder( url ).apply { body = sup diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 5c2d1cf..e708b1c 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 43a22ac..4d9ca16 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#Thu Jul 04 19:09:21 CEST 2019 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip diff --git a/gradlew b/gradlew index b0d6d0a..4f906e0 100755 --- a/gradlew +++ b/gradlew @@ -7,7 +7,7 @@ # 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 +# https://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, @@ -82,6 +82,7 @@ esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then @@ -125,10 +126,11 @@ if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath @@ -154,19 +156,19 @@ if $cygwin ; then else eval `echo args$i`="\"$arg\"" fi - i=$((i+1)) + i=`expr $i + 1` done case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi @@ -175,14 +177,9 @@ save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } -APP_ARGS=$(save "$@") +APP_ARGS=`save "$@"` # Collect all arguments for the java command, following the shell quoting and substitution rules eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" -# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong -if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then - cd "$(dirname "$0")" -fi - exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index 15e1ee3..ac1b06f 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -5,7 +5,7 @@ @rem you may not use this file except in compliance with the License. @rem You may obtain a copy of the License at @rem -@rem http://www.apache.org/licenses/LICENSE-2.0 +@rem https://www.apache.org/licenses/LICENSE-2.0 @rem @rem Unless required by applicable law or agreed to in writing, software @rem distributed under the License is distributed on an "AS IS" BASIS, @@ -29,6 +29,9 @@ if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @@ -37,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if "%ERRORLEVEL%" == "0" goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -51,7 +54,7 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% @@ -61,28 +64,14 @@ echo location of your Java installation. goto fail -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell diff --git a/service/build.gradle.kts b/service/build.gradle.kts index 7b83ec1..f961b83 100644 --- a/service/build.gradle.kts +++ b/service/build.gradle.kts @@ -2,6 +2,11 @@ val kotlinVersion: String by project val ktorVersion: String by project val junitVersion: String by project val ktorNettyVersion: String by project +val mockkVersion = "1.9.3" + +kotlin { + explicitApi() +} dependencies { implementation(kotlin("stdlib", kotlinVersion)) @@ -17,6 +22,7 @@ dependencies { implementation("org.apache.httpcomponents:httpclient:4.5") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.2") + testImplementation("io.mockk:mockk:$mockkVersion") testImplementation("junit:junit:$junitVersion") testImplementation(project(":test-utils")) testImplementation("io.ktor:ktor-client-mock-jvm:$ktorVersion") diff --git a/service/src/main/kotlin/net/devslash/GenericRequestData.kt b/service/src/main/kotlin/net/devslash/GenericRequestData.kt new file mode 100644 index 0000000..35ec95d --- /dev/null +++ b/service/src/main/kotlin/net/devslash/GenericRequestData.kt @@ -0,0 +1,28 @@ +package net.devslash + +import net.devslash.data.Acceptor +import net.devslash.data.DataClassAcceptor + +/** + * This is a one-shot. Not a data supplier. Therefore this simply has to accept a list and provide it in subsequent + * calls + */ +public class GenericRequestData(private val obj: T) : RequestData { + + override fun get(): T = obj + private val acceptor: Acceptor = visit(obj) + + private fun visit(obj: T): Acceptor { + return when (obj) { + else -> DataClassAcceptor(obj) + } + } + + public override fun getReplacements(): Map { + throw NotImplementedError() + } + + override fun accept(v: String): String { + return acceptor.accept(v) + } +} diff --git a/service/src/main/kotlin/net/devslash/HttpDriver.kt b/service/src/main/kotlin/net/devslash/HttpDriver.kt index 71f7d3a..5e5d7be 100644 --- a/service/src/main/kotlin/net/devslash/HttpDriver.kt +++ b/service/src/main/kotlin/net/devslash/HttpDriver.kt @@ -1,27 +1,20 @@ package net.devslash import com.fasterxml.jackson.databind.ObjectMapper -import io.ktor.client.HttpClient -import io.ktor.client.call.call -import io.ktor.client.engine.apache.Apache -import io.ktor.client.request.forms.FormDataContent -import io.ktor.client.request.headers -import io.ktor.content.ByteArrayContent -import io.ktor.content.TextContent -import io.ktor.http.ContentType -import io.ktor.http.Headers -import io.ktor.http.Parameters -import io.ktor.util.cio.toByteArray +import io.ktor.client.* +import io.ktor.client.call.* +import io.ktor.client.engine.apache.* +import io.ktor.client.request.* +import io.ktor.client.request.forms.* +import io.ktor.content.* +import io.ktor.http.* +import io.ktor.util.cio.* import java.net.URL -import kotlin.collections.List -import kotlin.collections.Map import kotlin.collections.component1 import kotlin.collections.component2 -import kotlin.collections.forEach -import kotlin.collections.mutableMapOf import kotlin.collections.set -class HttpDriver(config: Config) : AutoCloseable { +internal class HttpDriver(config: Config) : AutoCloseable { private val client = HttpClient(Apache) { engine { @@ -47,14 +40,17 @@ class HttpDriver(config: Config) : AutoCloseable { } when (req.body) { is JsonBody -> { - body = TextContent(mapper.writeValueAsString((req.body as JsonBody).get()), - ContentType.Application.Json) + val text = (req.body as JsonBody).get() + body = TextContent( + mapper.writeValueAsString(text), + ContentType.Application.Json + ) } - is BasicBodyProvider -> { - body = ByteArrayContent((req.body as BasicBodyProvider).get().toByteArray()) + is BasicBodyProvider<*> -> { + body = ByteArrayContent((req.body as BasicBodyProvider<*>).get().toByteArray()) } - is FormBody -> body = FormDataContent(Parameters.build { - val prov = req.body as FormBody + is FormBody<*> -> body = FormDataContent(Parameters.build { + val prov = req.body as FormBody<*> prov.get().forEach { (key, value) -> value.forEach { append(key, it) @@ -81,12 +77,14 @@ class HttpDriver(config: Config) : AutoCloseable { } } - suspend fun mapResponse(request: io.ktor.client.response.HttpResponse): HttpResponse { + internal suspend fun mapResponse(request: io.ktor.client.response.HttpResponse): HttpResponse { val response = request.call.response - return HttpResponse(URL(request.call.request.url.toString()), - response.status.value, - mapHeaders(response.headers), - response.content.toByteArray()) + return HttpResponse( + URL(request.call.request.url.toString()), + response.status.value, + mapHeaders(response.headers), + response.content.toByteArray() + ) } private fun mapHeaders(headers: Headers): Map> { diff --git a/service/src/main/kotlin/net/devslash/HttpSessionManager.kt b/service/src/main/kotlin/net/devslash/HttpSessionManager.kt index 3812485..a928082 100644 --- a/service/src/main/kotlin/net/devslash/HttpSessionManager.kt +++ b/service/src/main/kotlin/net/devslash/HttpSessionManager.kt @@ -7,9 +7,9 @@ import java.util.concurrent.Executors import java.util.concurrent.Semaphore import kotlin.coroutines.CoroutineContext -typealias Contents = Pair +private typealias Contents = Pair> -class HttpSessionManager(val engine: HttpDriver, private val session: Session) : SessionManager { +internal class HttpSessionManager(val engine: HttpDriver, private val session: Session) : SessionManager { private val semaphore = Semaphore(0) private var lastCall = 0L @@ -24,38 +24,48 @@ class HttpSessionManager(val engine: HttpDriver, private val session: Session) : } } - private suspend fun produceHttp(call: Call, - jar: CookieJar, channel: Channel>) { + private suspend fun produceHttp( + call: Call, + jar: CookieJar, channel: Channel>> + ) { val dataSupplier = handleNoSupplier(call.dataSupplier) while (true) { - val data = dataSupplier.getDataForRequest() ?: break + // This is _relatively_ safe because the type T is captured in the build of the call. Someone using the DSL + // can't stuff this up + @Suppress("UNCHECKED_CAST") + val data = dataSupplier.getDataForRequest() as RequestData? ?: break val req = mapHttpRequest(call, data) // Call the prerequesites val preRequest = call.beforeHooks.toMutableList() preRequest.add(jar) val shouldSkip = - preRequest.filter { it is SkipBeforeHook }.any { (it as SkipBeforeHook).skip(data) } + preRequest.filter { it is SkipBeforeHook<*> }.any { (it as SkipBeforeHook).skip(data) } if (shouldSkip) continue preRequest.forEach { when (it) { is SimpleBeforeHook -> it.accept(req, data) - is SessionPersistingBeforeHook -> it.accept(sessionManager, jar, req, data) + is SessionPersistingBeforeHook -> it.accept( + sessionManager, + jar, + req, + data + ) } } call.headers?.forEach { entry -> entry.value.forEach { val s = when (it) { is StrValue -> it.value - is ProvidedValue -> it.lambda(data) + is ProvidedValue<*> -> (it as ProvidedValue).lambda(data) } req.addHeader(entry.key, s) } } - if(channel.offer(Envelope(Pair(req, data)))) { + if (channel.offer(Envelope(Pair(req, data)))) { continue } else { channel.send(Envelope(Pair(req, data))) @@ -64,14 +74,15 @@ class HttpSessionManager(val engine: HttpDriver, private val session: Session) : channel.close() } - override fun call(call: Call) = call(call, CookieJar()) + override fun call(call: Call) = call(call, CookieJar()) - override fun call(call: Call, jar: CookieJar) = runBlocking { + + override fun call(call: Call, jar: CookieJar) = runBlocking { // Okay, so in here we're going to do the one to many calls we have to to get this to run. - val channel: Channel>> = - Channel(session.concurrency * 2) + val channel: Channel>>> = + Channel(session.concurrency * 2) val produceThreadPool = System.getProperty("PRODUCE_THREAD_POOL_SIZE")?.toInt() - ?: Runtime.getRuntime().availableProcessors() + ?: Runtime.getRuntime().availableProcessors() val produceExecutor = Executors.newFixedThreadPool(produceThreadPool) val produceDispatcher = produceExecutor.asCoroutineDispatcher() launch(produceDispatcher) { produceHttp(call, jar, channel) } @@ -99,11 +110,13 @@ class HttpSessionManager(val engine: HttpDriver, private val session: Session) : val jobs = mutableListOf() val concurrency = if (hasDelay) 1 else session.concurrency repeat(concurrency) { - jobs += launchHttpProcessor(call, - limiter, - afterRequest, - channel, - dispatcher) + jobs += launchHttpProcessor( + call, + limiter, + afterRequest, + channel, + dispatcher + ) } jobs.joinAll() produceExecutor.shutdownNow() @@ -111,11 +124,13 @@ class HttpSessionManager(val engine: HttpDriver, private val session: Session) : Unit } - private fun CoroutineScope.launchHttpProcessor(call: Call, - rateLimiter: AcquiringRateLimiter, - afterRequest: List, - channel: Channel>>, - dispatcher: CoroutineContext) = launch(dispatcher) { + private fun CoroutineScope.launchHttpProcessor( + call: Call, + rateLimiter: AcquiringRateLimiter, + afterRequest: List, + channel: Channel>>>, + dispatcher: CoroutineContext + ) = launch(dispatcher) { for (next in channel) { // ensure that this is a valid request if (next.shouldProceed()) { @@ -140,14 +155,16 @@ class HttpSessionManager(val engine: HttpDriver, private val session: Session) : } } - private suspend fun handleFailure(call: Call, - channel: Channel>>, - next: Envelope>, - request: Failure) { + private suspend fun handleFailure( + call: Call, + channel: Channel>>>, + next: Envelope>>, + request: Failure + ) { call.onError?.let { when (it) { is ChannelReceiving<*> -> { - (it as ChannelReceiving).accept(channel, next, request.err) + (it as ChannelReceiving>).accept(channel, next, request.err) } else -> { throw request.err @@ -156,7 +173,7 @@ class HttpSessionManager(val engine: HttpDriver, private val session: Session) : } } - private fun mapHttpRequest(call: Call, data: RequestData): HttpRequest { + private fun mapHttpRequest(call: Call, data: RequestData): HttpRequest { val url = getUrlProvider(call, data) val body = getBodyProvider(call, data) val currentUrl = url.get() diff --git a/service/src/main/kotlin/net/devslash/ListBasedRequestData.kt b/service/src/main/kotlin/net/devslash/ListBasedRequestData.kt index 331e5f3..caa1913 100644 --- a/service/src/main/kotlin/net/devslash/ListBasedRequestData.kt +++ b/service/src/main/kotlin/net/devslash/ListBasedRequestData.kt @@ -1,24 +1,30 @@ package net.devslash -class ListBasedRequestData(private val parts: List = listOf()) : RequestData { - override fun getReplacements(): Map { - return parts.mapIndexed { index, string -> - "!" + (index + 1) + "!" to string - }.toMap() - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false +/** + * This is a one-shot. Not a data supplier. Therefore this simply has to accept a list and provide it in subsequent + * calls + */ +public class ListBasedRequestData> private constructor( + private val parts: T, +) : RequestData { - other as ListBasedRequestData - if (parts != other.parts) return false + public companion object { + public operator fun invoke(list: List): ListBasedRequestData> { + return ListBasedRequestData(list); + } + } - return true + override fun getReplacements(): Map { + return parts.mapIndexed { index, obj -> + "!" + (index + 1) + "!" to obj.toString() + }.toMap() } - override fun hashCode(): Int { - return parts.hashCode() + override fun get(): T = parts + override fun accept(v: String): String { + var x = v + parts.forEachIndexed { index, e -> x = x.replace("!${index + 1}!", e.toString()) } + return x } } diff --git a/service/src/main/kotlin/net/devslash/ListStringAcceptor.kt b/service/src/main/kotlin/net/devslash/ListStringAcceptor.kt new file mode 100644 index 0000000..a17b35b --- /dev/null +++ b/service/src/main/kotlin/net/devslash/ListStringAcceptor.kt @@ -0,0 +1,35 @@ +package net.devslash.data + +import java.lang.reflect.ParameterizedType +import java.lang.reflect.Type + +public fun interface Acceptor { + public fun accept(v: String): String +} + +@Target(AnnotationTarget.FIELD) +@Retention(AnnotationRetention.RUNTIME) +public annotation class Capture(val replace: String) + +public val NoOpAcceptor: Acceptor = Acceptor { it } + +private abstract class TypeReference : Comparable> { + val type: Type = + (javaClass.genericSuperclass as ParameterizedType).actualTypeArguments[0] + + override fun compareTo(other: TypeReference) = 0 +} + +public fun DataClassAcceptor(v: T): Acceptor { + val type = v!!::class.java + val fieldPairs = type.declaredFields.filter { it.getAnnotation(Capture::class.java) != null } + .map { it to it.getDeclaredAnnotation(Capture::class.java).replace } + return Acceptor { + var ret = it + fieldPairs.forEach { pair -> + val backer = v!!::class.members.find { it.name == pair.first.name }!! + ret = it.replace(pair.second, backer.call(v).toString()) + } + ret + } +} \ No newline at end of file diff --git a/service/src/main/kotlin/net/devslash/Providers.kt b/service/src/main/kotlin/net/devslash/Providers.kt index acf59b2..fac657a 100644 --- a/service/src/main/kotlin/net/devslash/Providers.kt +++ b/service/src/main/kotlin/net/devslash/Providers.kt @@ -2,11 +2,11 @@ package net.devslash import java.util.concurrent.atomic.AtomicBoolean -interface URLProvider { +internal interface URLProvider { fun get(): String } -fun handleNoSupplier(data: RequestDataSupplier?): RequestDataSupplier { +internal fun handleNoSupplier(data: RequestDataSupplier<*>?): RequestDataSupplier<*> { if (data != null) { data.init() return data @@ -16,15 +16,26 @@ fun handleNoSupplier(data: RequestDataSupplier?): RequestDataSupplier { return SingleUseDataSupplier() } -class SingleUseDataSupplier(private val supply: Map = mapOf()) : RequestDataSupplier { +public class SingleUseDataSupplier(private val supply: Map = mapOf()) : RequestDataSupplier> { private val first = AtomicBoolean(true) - override suspend fun getDataForRequest(): RequestData? { + override suspend fun getDataForRequest(): RequestData>? { if (first.compareAndSet(true, false)) { - return object : RequestData { + return object : RequestData> { override fun getReplacements(): Map { return supply } + + override fun get(): Map { + return supply + } + + override fun accept(v: String): String { + supply.forEach { (key, value) -> + v.replace(key, value) + } + return v + } } } return null diff --git a/service/src/main/kotlin/net/devslash/RateLimiter.kt b/service/src/main/kotlin/net/devslash/RateLimiter.kt index 8be6c51..e1042ce 100644 --- a/service/src/main/kotlin/net/devslash/RateLimiter.kt +++ b/service/src/main/kotlin/net/devslash/RateLimiter.kt @@ -15,7 +15,7 @@ import kotlin.math.max * This means that the rate limiter effectively has no memory of the past, beyond the call before. This means that if * there is inconsistency with how fast requests are performed, then the rate limit may be over-restrictive. */ -class AcquiringRateLimiter(private val rateLimitOptions: RateLimitOptions, private val clock: Clock = Clock.systemUTC()) { +internal class AcquiringRateLimiter(private val rateLimitOptions: RateLimitOptions, private val clock: Clock = Clock.systemUTC()) { private var lastRelease = Instant.ofEpochMilli(0) // This equals how many milliseconds it takes to release a ticket private val qps = rateLimitOptions.duration.toMillis() / max(1, rateLimitOptions.count) diff --git a/service/src/main/kotlin/net/devslash/Runner.kt b/service/src/main/kotlin/net/devslash/Runner.kt index aed5485..ac0d049 100644 --- a/service/src/main/kotlin/net/devslash/Runner.kt +++ b/service/src/main/kotlin/net/devslash/Runner.kt @@ -5,36 +5,36 @@ package net.devslash * Use `0` to specify infinite. * Negative value mean to use the system's default value. */ -class ConfigBuilder { +public class ConfigBuilder { /** * Max time between TCP packets - default 10 seconds. */ - var followRedirects: Boolean = false + public var followRedirects: Boolean = false /** * Max time to establish an HTTP connection - default 10 seconds. */ - var socketTimeout: Int = 10_000 + public var socketTimeout: Int = 10_000 /** * Max time to establish an HTTP connection - default 10 seconds. */ - var connectTimeout: Int = 20_000 + public var connectTimeout: Int = 20_000 /** * Max time for the connection manager to start a request - 20 seconds. */ - var connectionRequestTimeout = 20_000 + public var connectionRequestTimeout: Int = 20_000 - fun build(): Config { + internal fun build(): Config { return Config(followRedirects, socketTimeout, connectTimeout, connectionRequestTimeout) } } -data class Config(val followRedirects: Boolean, val socketTimeout: Int, val connectTimeout: Int, val connectionRequestTimeout: Int) +internal data class Config(val followRedirects: Boolean, val socketTimeout: Int, val connectTimeout: Int, val connectionRequestTimeout: Int) -fun runHttp(block: SessionBuilder.() -> Unit) { +public fun runHttp(block: SessionBuilder.() -> Unit) { return runHttp({}, block) } -fun runHttp(config: ConfigBuilder.() -> Unit, block: SessionBuilder.() -> Unit) { +public fun runHttp(config: ConfigBuilder.() -> Unit, block: SessionBuilder.() -> Unit) { return runHttp(HttpDriver(ConfigBuilder().apply(config).build()), block) } diff --git a/service/src/main/kotlin/net/devslash/UrlProviders.kt b/service/src/main/kotlin/net/devslash/UrlProviders.kt index 0f630df..b8545d6 100644 --- a/service/src/main/kotlin/net/devslash/UrlProviders.kt +++ b/service/src/main/kotlin/net/devslash/UrlProviders.kt @@ -1,12 +1,14 @@ package net.devslash -private class OverwrittenUrlProvider(private val url: String, - private val replacements: RequestData) : URLProvider { +private class OverwrittenUrlProvider( + private val url: String, + private val replacements: RequestData +) : URLProvider { override fun get(): String { - return url.asReplaceableValue().get(replacements) + return replacements.accept(url) } } -fun getUrlProvider(call: Call, data: RequestData): URLProvider { +internal fun getUrlProvider(call: Call, data: RequestData): URLProvider { return OverwrittenUrlProvider(call.url, data) } diff --git a/service/src/test/kotlin/net/devslash/DefaultHeaderTest.kt b/service/src/test/kotlin/net/devslash/DefaultHeaderTest.kt index ab45d61..8d83962 100644 --- a/service/src/test/kotlin/net/devslash/DefaultHeaderTest.kt +++ b/service/src/test/kotlin/net/devslash/DefaultHeaderTest.kt @@ -8,7 +8,7 @@ class DefaultHeaderTest { @Test fun testIfNoHeaderSetThenUserAgentExists() { - val call = CallBuilder("http://example.com").build() + val call = CallBuilder("http://example.com").build() assertThat(call.headers, equalTo(mapOf>("User-Agent" to listOf(StrValue("FetchDSL (Apache-HttpAsyncClient + Kotlin, version not set)"))))) @@ -16,7 +16,7 @@ class DefaultHeaderTest { @Test fun testIfUserAgentSetItIsNotOverwritten() { - val call = CallBuilder("http://example.com").apply { + val call = CallBuilder("http://example.com").apply { headers = mapOf>("User-Agent" to listOf("OVERRIDE")) }.build() diff --git a/extensions/src/test/kotlin/net/devslash/DelayTest.kt b/service/src/test/kotlin/net/devslash/DelayTest.kt similarity index 58% rename from extensions/src/test/kotlin/net/devslash/DelayTest.kt rename to service/src/test/kotlin/net/devslash/DelayTest.kt index 2c0a9fb..83a4cfe 100644 --- a/extensions/src/test/kotlin/net/devslash/DelayTest.kt +++ b/service/src/test/kotlin/net/devslash/DelayTest.kt @@ -3,10 +3,14 @@ package net.devslash import io.ktor.client.response.HttpResponse import io.mockk.coEvery import io.mockk.mockk -import net.devslash.data.ListDataSupplier -import net.devslash.util.getResponseWithBody import org.junit.Assert.assertTrue import org.junit.Test +import java.net.URL +import java.util.concurrent.atomic.AtomicInteger + +fun getResponseWithBody(body: ByteArray): net.devslash.HttpResponse { + return HttpResponse(URL("http://example.com"), 200, mapOf(), body) +} class DelayTest { @@ -25,7 +29,16 @@ class DelayTest { HttpSessionManager(engine, SessionBuilder().apply { delay = 30 call("http://example.org") { - data = ListDataSupplier(listOf(1, 2)) + data = object : RequestDataSupplier> { + val count = AtomicInteger(0) + override suspend fun getDataForRequest(): RequestData>? { + if (count.incrementAndGet() > 2) { + return null + } + return ListBasedRequestData(listOf()) + } + + } } }.build()).run() diff --git a/service/src/test/kotlin/net/devslash/ListBasedRequestDataTest.kt b/service/src/test/kotlin/net/devslash/ListBasedRequestDataTest.kt index c17da32..adcb24c 100644 --- a/service/src/test/kotlin/net/devslash/ListBasedRequestDataTest.kt +++ b/service/src/test/kotlin/net/devslash/ListBasedRequestDataTest.kt @@ -8,7 +8,7 @@ internal class ListBasedRequestDataTest { @Test fun testEmptyRequestData() { - val data = ListBasedRequestData(emptyList()) + val data = ListBasedRequestData(emptyList()) assertThat(data.getReplacements(), equalTo(emptyMap())) } diff --git a/service/src/test/kotlin/net/devslash/TestUtils.kt b/service/src/test/kotlin/net/devslash/TestUtils.kt new file mode 100644 index 0000000..ef2a91d --- /dev/null +++ b/service/src/test/kotlin/net/devslash/TestUtils.kt @@ -0,0 +1,40 @@ +package net.devslash + +fun requestDataFromList(listOf: List? = null): RequestData { + return object : RequestData { + override fun getReplacements(): Map { + if (listOf != null) { + return listOf.mapIndexed { i, p -> + "!${i + 1}!" to p + }.toMap() + } + + return mapOf() + } + + override fun get(): String { + TODO("Not yet implemented") + } + + override fun accept(v: String): String { + val replacements = getReplacements() + var copy = v + replacements.forEach { (key, value) -> copy = copy.replace(key, value) } + return copy + } + } +} + +fun getBasicRequest(): HttpRequest { + return HttpRequest(HttpMethod.GET, "https://example.com", EmptyBodyProvider) +} + +fun getCookieJar(): CookieJar { + return CookieJar() +} + +internal fun getCall(sup: HttpBody? = null, url: String = "https://example.com") = CallBuilder( + url +).apply { + body = sup +}.build() diff --git a/extensions/src/test/kotlin/net/devslash/UrlProvidersTest.kt b/service/src/test/kotlin/net/devslash/UrlProvidersTest.kt similarity index 87% rename from extensions/src/test/kotlin/net/devslash/UrlProvidersTest.kt rename to service/src/test/kotlin/net/devslash/UrlProvidersTest.kt index cd35b94..6b91a70 100644 --- a/extensions/src/test/kotlin/net/devslash/UrlProvidersTest.kt +++ b/service/src/test/kotlin/net/devslash/UrlProvidersTest.kt @@ -1,7 +1,5 @@ package net.devslash -import net.devslash.util.getCall -import net.devslash.util.requestDataFromList import org.junit.Assert.assertEquals import org.junit.Test