From 88559c0257e2c28e0937cdfc2d2d4b9001d28d81 Mon Sep 17 00:00:00 2001
From: i-walker <46971368+i-walker@users.noreply.github.com>
Date: Mon, 25 Oct 2021 01:21:32 +0200
Subject: [PATCH 01/38] progress
---
core/build.gradle.kts | 9 +-
.../kotlin/arrow/endpoint/ArrowEndpoint.kt | 22 +-
.../commonMain/kotlin/arrow/endpoint/Codec.kt | 282 ++++++++
.../kotlin/arrow/endpoint/Endpoint.kt | 1 +
.../kotlin/arrow/endpoint/EndpointIO.kt | 0
.../kotlin/arrow/endpoint/EndpointInput.kt | 1 +
.../arrow/endpoint/EndpointInterceptor.kt | 0
.../kotlin/arrow/endpoint/EndpointOutput.kt | 1 +
.../kotlin/arrow/endpoint/EndpointTransput.kt | 0
.../kotlin/arrow/endpoint/FieldName.kt | 0
.../kotlin/arrow/endpoint/Mapping.kt | 0
.../kotlin/arrow/endpoint/MethodSyntax.kt | 1 +
.../kotlin/arrow/endpoint/Params.kt | 0
.../kotlin/arrow/endpoint/Schema.kt | 465 +++++++++++++
.../arrow/endpoint/client/RequestInfo.kt | 0
.../arrow/endpoint/model/CodecFormat.kt | 0
.../kotlin/arrow/endpoint/model/Cookie.kt | 0
.../kotlin/arrow/endpoint/model/Header.kt | 1 -
.../kotlin/arrow/endpoint/model/MediaType.kt | 5 +-
.../kotlin/arrow/endpoint/model/Method.kt | 1 -
.../arrow/endpoint/model/QueryParams.kt | 1 -
.../arrow/endpoint/model/RequestMetadata.kt | 77 +++
.../kotlin/arrow/endpoint/model/Rfc2616.kt | 0
.../kotlin/arrow/endpoint/model/Rfc3986.kt | 21 +-
.../kotlin/arrow/endpoint/model/StatusCode.kt | 1 -
.../kotlin/arrow/endpoint/model/Uri.kt | 610 +++++++++++++++++
.../kotlin/arrow/endpoint/predef.kt | 36 +
.../arrow/endpoint/server/ServerEndpoint.kt | 0
.../interpreter/DecodeBasicInputsResult.kt | 2 +-
.../server/interpreter/InputValueResult.kt | 0
.../server/interpreter/OutputValues.kt | 0
.../server/interpreter/RequestBody.kt | 0
.../server/interpreter/ServerInterpreter.kt | 2 +
.../jvmMain/kotlin/arrow/endpoint/Codec.kt | 371 ++---------
.../jvmMain/kotlin/arrow/endpoint/Schema.kt | 519 +--------------
.../arrow/endpoint/model/RequestMetadata.kt | 92 +--
.../kotlin/arrow/endpoint/model/Uri.kt | 630 +-----------------
.../jvmMain/kotlin/arrow/endpoint/predef.kt | 34 -
.../ktor/server/KtorHttpServerInterpreter.kt | 5 +-
39 files changed, 1625 insertions(+), 1565 deletions(-)
rename core/src/{jvmMain => commonMain}/kotlin/arrow/endpoint/ArrowEndpoint.kt (93%)
create mode 100644 core/src/commonMain/kotlin/arrow/endpoint/Codec.kt
rename core/src/{jvmMain => commonMain}/kotlin/arrow/endpoint/Endpoint.kt (99%)
rename core/src/{jvmMain => commonMain}/kotlin/arrow/endpoint/EndpointIO.kt (100%)
rename core/src/{jvmMain => commonMain}/kotlin/arrow/endpoint/EndpointInput.kt (99%)
rename core/src/{jvmMain => commonMain}/kotlin/arrow/endpoint/EndpointInterceptor.kt (100%)
rename core/src/{jvmMain => commonMain}/kotlin/arrow/endpoint/EndpointOutput.kt (99%)
rename core/src/{jvmMain => commonMain}/kotlin/arrow/endpoint/EndpointTransput.kt (100%)
rename core/src/{jvmMain => commonMain}/kotlin/arrow/endpoint/FieldName.kt (100%)
rename core/src/{jvmMain => commonMain}/kotlin/arrow/endpoint/Mapping.kt (100%)
rename core/src/{jvmMain => commonMain}/kotlin/arrow/endpoint/MethodSyntax.kt (99%)
rename core/src/{jvmMain => commonMain}/kotlin/arrow/endpoint/Params.kt (100%)
create mode 100644 core/src/commonMain/kotlin/arrow/endpoint/Schema.kt
rename core/src/{jvmMain => commonMain}/kotlin/arrow/endpoint/client/RequestInfo.kt (100%)
rename core/src/{jvmMain => commonMain}/kotlin/arrow/endpoint/model/CodecFormat.kt (100%)
rename core/src/{jvmMain => commonMain}/kotlin/arrow/endpoint/model/Cookie.kt (100%)
rename core/src/{jvmMain => commonMain}/kotlin/arrow/endpoint/model/Header.kt (99%)
rename core/src/{jvmMain => commonMain}/kotlin/arrow/endpoint/model/MediaType.kt (94%)
rename core/src/{jvmMain => commonMain}/kotlin/arrow/endpoint/model/Method.kt (99%)
rename core/src/{jvmMain => commonMain}/kotlin/arrow/endpoint/model/QueryParams.kt (98%)
create mode 100644 core/src/commonMain/kotlin/arrow/endpoint/model/RequestMetadata.kt
rename core/src/{jvmMain => commonMain}/kotlin/arrow/endpoint/model/Rfc2616.kt (100%)
rename core/src/{jvmMain => commonMain}/kotlin/arrow/endpoint/model/Rfc3986.kt (83%)
rename core/src/{jvmMain => commonMain}/kotlin/arrow/endpoint/model/StatusCode.kt (99%)
create mode 100644 core/src/commonMain/kotlin/arrow/endpoint/model/Uri.kt
create mode 100644 core/src/commonMain/kotlin/arrow/endpoint/predef.kt
rename core/src/{jvmMain => commonMain}/kotlin/arrow/endpoint/server/ServerEndpoint.kt (100%)
rename core/src/{jvmMain => commonMain}/kotlin/arrow/endpoint/server/interpreter/DecodeBasicInputsResult.kt (99%)
rename core/src/{jvmMain => commonMain}/kotlin/arrow/endpoint/server/interpreter/InputValueResult.kt (100%)
rename core/src/{jvmMain => commonMain}/kotlin/arrow/endpoint/server/interpreter/OutputValues.kt (100%)
rename core/src/{jvmMain => commonMain}/kotlin/arrow/endpoint/server/interpreter/RequestBody.kt (100%)
rename core/src/{jvmMain => commonMain}/kotlin/arrow/endpoint/server/interpreter/ServerInterpreter.kt (98%)
diff --git a/core/build.gradle.kts b/core/build.gradle.kts
index de27b0a4..bd0e8d3d 100644
--- a/core/build.gradle.kts
+++ b/core/build.gradle.kts
@@ -1,11 +1,18 @@
kotlin {
sourceSets {
commonMain {
+ dependencies {
+ implementation(kotlin("stdlib", Version.kotlin))
+ implementation(Libs.kotlinxCoroutines)
+ implementation(Libs.ktorio)
+ }
+ }
+
+ jvmMain {
dependencies {
// Needed for Uri MatchNamedGroupCollection, ties us to JDK8
// TODO https://app.clickup.com/t/kt7qd2
implementation(kotlin("stdlib-jdk8", Version.kotlin))
- implementation(Libs.kotlinxCoroutines)
}
}
}
diff --git a/core/src/jvmMain/kotlin/arrow/endpoint/ArrowEndpoint.kt b/core/src/commonMain/kotlin/arrow/endpoint/ArrowEndpoint.kt
similarity index 93%
rename from core/src/jvmMain/kotlin/arrow/endpoint/ArrowEndpoint.kt
rename to core/src/commonMain/kotlin/arrow/endpoint/ArrowEndpoint.kt
index 4afbfb53..1a61b246 100644
--- a/core/src/jvmMain/kotlin/arrow/endpoint/ArrowEndpoint.kt
+++ b/core/src/commonMain/kotlin/arrow/endpoint/ArrowEndpoint.kt
@@ -8,17 +8,17 @@ import arrow.endpoint.model.Header
import arrow.endpoint.model.Method
import arrow.endpoint.model.QueryParams
import arrow.endpoint.model.StatusCode
-import java.io.InputStream
-import java.nio.ByteBuffer
-import java.nio.charset.Charset
-import java.nio.charset.StandardCharsets
+import io.ktor.utils.io.ByteChannel
+import io.ktor.utils.io.ByteReadChannel
+import io.ktor.utils.io.charsets.Charset
+import io.ktor.utils.io.charsets.Charsets
// Turn into top-level functions?
public object ArrowEndpoint {
public inline operator fun invoke(f: ArrowEndpoint.() -> A): A = f(ArrowEndpoint)
- @JvmName("queryList")
+ //@JvmName("queryList")
public fun query(name: String, codec: Codec, A, CodecFormat.TextPlain>): EndpointInput.Query =
EndpointInput.Query(name, codec, EndpointIO.Info.empty())
@@ -60,26 +60,26 @@ public object ArrowEndpoint {
public fun stringBody(charset: String): EndpointIO.StringBody =
stringBody(Charset.forName(charset))
- public fun stringBody(charset: Charset = StandardCharsets.UTF_8): EndpointIO.StringBody =
+ public fun stringBody(charset: Charset = Charsets.UTF_8): EndpointIO.StringBody =
EndpointIO.StringBody(charset, Codec.string, EndpointIO.Info.empty())
public val htmlBodyUtf8: EndpointIO.StringBody =
EndpointIO.StringBody(
- StandardCharsets.UTF_8,
+ Charsets.UTF_8,
Codec.string.format(CodecFormat.TextHtml),
EndpointIO.Info.empty()
)
public fun plainBody(
codec: PlainCodec,
- charset: Charset = StandardCharsets.UTF_8
+ charset: Charset = Charsets.UTF_8
): EndpointIO.StringBody =
EndpointIO.StringBody(charset, codec, EndpointIO.Info.empty())
/** A body in any format, read using the given `codec`, from a raw string read using `charset`.*/
public fun anyFromStringBody(
codec: Codec,
- charset: Charset = StandardCharsets.UTF_8
+ charset: Charset = Charsets.UTF_8
): EndpointIO.StringBody =
EndpointIO.StringBody(charset, codec, EndpointIO.Info.empty())
@@ -100,10 +100,10 @@ public object ArrowEndpoint {
public fun byteArrayBody(): EndpointIO.ByteArrayBody =
EndpointIO.ByteArrayBody(Codec.byteArray, EndpointIO.Info.empty())
- public fun byteBufferBody(): EndpointIO.ByteBufferBody =
+ public fun byteBufferBody(): EndpointIO.ByteBufferBody =
EndpointIO.ByteBufferBody(Codec.byteBuffer, EndpointIO.Info.empty())
- public fun inputStreamBody(): EndpointIO.InputStreamBody =
+ public fun inputStreamBody(): EndpointIO.InputStreamBody =
EndpointIO.InputStreamBody(Codec.inputStream, EndpointIO.Info.empty())
public fun formBody(codec: Codec): EndpointIO.StringBody =
diff --git a/core/src/commonMain/kotlin/arrow/endpoint/Codec.kt b/core/src/commonMain/kotlin/arrow/endpoint/Codec.kt
new file mode 100644
index 00000000..06cb8bfe
--- /dev/null
+++ b/core/src/commonMain/kotlin/arrow/endpoint/Codec.kt
@@ -0,0 +1,282 @@
+@file:Suppress("MemberVisibilityCanBePrivate")
+
+package arrow.endpoint
+
+import arrow.core.Either
+import arrow.core.None
+import arrow.core.Option
+import arrow.core.Some
+import arrow.core.andThen
+import arrow.endpoint.model.CodecFormat
+import arrow.endpoint.model.Cookie
+import arrow.endpoint.model.Uri
+import arrow.endpoint.model.UriError
+
+public typealias PlainCodec = Codec
+public typealias JsonCodec = Codec
+public typealias XmlCodec = Codec
+
+public interface Codec : Mapping {
+ public fun schema(): Schema
+ public val format: CF
+
+ public fun map(codec: Codec): Codec =
+ object : Codec {
+ override fun rawDecode(l: L): DecodeResult =
+ this@Codec.rawDecode(l).flatMap(codec::rawDecode)
+
+ override fun encode(h: HH): L =
+ this@Codec.encode(codec.encode(h))
+
+ override val format: CF = this@Codec.format
+
+ override fun schema(): Schema =
+ codec.schema()
+ }
+
+ override fun map(codec: Mapping): Codec =
+ object : Codec {
+ override fun rawDecode(l: L): DecodeResult =
+ this@Codec.rawDecode(l).flatMap(codec::rawDecode)
+
+ override fun encode(h: HH): L =
+ this@Codec.encode(codec.encode(h))
+
+ override val format: CF = this@Codec.format
+
+ override fun schema(): Schema =
+ this@Codec.schema()
+ .map { v ->
+ when (val res = codec.decode(v)) {
+ is DecodeResult.Failure -> null
+ is DecodeResult.Value -> res.value
+ }
+ }
+ }
+
+ public fun mapDecode(rawDecode: (H) -> DecodeResult, encode: (HH) -> H): Codec =
+ map(Mapping.fromDecode(rawDecode, encode))
+
+ public fun map(f: (H) -> HH, g: (HH) -> H): Codec =
+ mapDecode(f.andThen { DecodeResult.Value(it) }, g)
+
+ public fun schema(s2: Schema?): Codec =
+ s2?.let {
+ object : Codec {
+ override fun rawDecode(l: L): DecodeResult = this@Codec.decode(l)
+ override fun encode(h: H): L = this@Codec.encode(h)
+ override fun schema(): Schema = s2
+ override val format: CF = this@Codec.format
+ }
+ } ?: this@Codec
+
+ public fun modifySchema(modify: (Schema) -> Schema): Codec =
+ schema(modify(schema()))
+
+ public fun format(f: CF2): Codec =
+ object : Codec {
+ override fun rawDecode(l: L): DecodeResult = this@Codec.decode(l)
+ override fun encode(h: H): L = this@Codec.encode(h)
+ override fun schema(): Schema = this@Codec.schema()
+ override val format: CF2 = f
+ }
+
+ override fun decode(l: L): DecodeResult {
+ val res = super.decode(l)
+ val default = schema().info.default
+ return when {
+ res is DecodeResult.Failure.Missing && default != null ->
+ DecodeResult.Value(default.first)
+ else -> res
+ }
+ }
+
+ public companion object {
+ public fun id(f: CF, s: Schema): Codec =
+ object : Codec {
+ override fun rawDecode(l: L): DecodeResult = DecodeResult.Value(l)
+ override fun encode(h: L): L = h
+ override fun schema(): Schema = s
+ override val format: CF = f
+ }
+
+ public fun idPlain(s: Schema = Schema.string()): Codec =
+ id(CodecFormat.TextPlain, s)
+
+ public fun stringCodec(schema: Schema, parse: (String) -> T): Codec =
+ string.map(parse) { it.toString() }.schema(schema)
+
+ public val string: Codec =
+ id(CodecFormat.TextPlain, Schema.string)
+
+ public val byte: Codec = stringCodec(Schema.byte) { it.toByte() }
+ public val short: Codec = stringCodec(Schema.short) { it.toShort() }
+ public val int: Codec = stringCodec(Schema.int) { it.toInt() }
+ public val long: Codec = stringCodec(Schema.long) { it.toLong() }
+ public val float: Codec = stringCodec(Schema.float) { it.toFloat() }
+ public val double: Codec = stringCodec(Schema.double) { it.toDouble() }
+ public val boolean: Codec = stringCodec(Schema.boolean) { it.toBoolean() }
+
+ public val uri: PlainCodec =
+ string.mapDecode(
+ { raw ->
+ Uri.parse(raw).fold(
+ { _: UriError -> DecodeResult.Failure.Error(raw, IllegalArgumentException(this.toString())) },
+ { DecodeResult.Value(it) }
+ )
+ },
+ Uri::toString
+ )
+
+ public val byteArray: Codec =
+ id(CodecFormat.OctetStream, Schema.byteArray)
+
+ private fun listBinarySchema(c: Codec): Codec, List, CF> =
+ id(c.format, Schema.binary>())
+ .mapDecode({ aas -> aas.traverseDecodeResult(c::decode) }) { bbs -> bbs.map(c::encode) }
+
+ /**
+ * Create a codec which requires that a list of low-level values contains a single element. Otherwise a decode
+ * failure is returned. The given base codec `c` is used for decoding/encoding.
+ *
+ * The schema and validator are copied from the base codec.
+ */
+ public fun listFirst(c: Codec): Codec, B, CF> =
+ listBinarySchema(c)
+ .mapDecode({ list ->
+ when (list.size) {
+ 0 -> DecodeResult.Failure.Missing
+ 1 -> DecodeResult.Value(list[0])
+ else -> DecodeResult.Failure.Multiple(list)
+ }
+ }) {
+ listOf(it)
+ }
+ .schema(c.schema())
+
+ /**
+ * Create a codec which requires that a list of low-level values contains a single element. Otherwise a decode
+ * failure is returned. The given base codec `c` is used for decoding/encoding.
+ *
+ * The schema and validator are copied from the base codec.
+ */
+ public fun listFirstOrNull(c: Codec): Codec, B?, CF> =
+ listBinarySchema(c)
+ .mapDecode({ list ->
+ when (list.size) {
+ 0 -> DecodeResult.Value(null)
+ 1 -> DecodeResult.Value(list[0])
+ else -> DecodeResult.Failure.Multiple(list)
+ }
+ }) { listOfNotNull(it) }
+ .schema(c.schema().asNullable())
+
+ /**
+ * Create a codec which requires that a nullable low-level representation contains a single element.
+ * Otherwise a decode failure is returned. The given base codec `c` is used for decoding/encoding.
+ *
+ * The schema and validator are copied from the base codec.
+ */
+ public fun nullableFirst(c: Codec): Codec =
+ id(c.format, Schema.binary())
+ .mapDecode({ option ->
+ when (option) {
+ null -> DecodeResult.Failure.Missing
+ else -> c.decode(option)
+ }
+ }) { us -> us?.let(c::encode) }
+ .schema(c.schema())
+
+ /**
+ * Create a codec which decodes/encodes a list of low-level values to a list of high-level values, using the given base codec `c`.
+ *
+ * The schema is copied from the base codec.
+ */
+ public fun list(c: Codec): Codec, List, CF> =
+ listBinarySchema(c).schema(c.schema().asList())
+
+ /**
+ * Create a codec which decodes/encodes an optional low-level value to an optional high-level value.
+ * The given base codec `c` is used for decoding/encoding.
+ *
+ * The schema and validator are copied from the base codec.
+ */
+ public fun option(c: Codec): Codec