Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,5 @@ include(
"src:integration:jackson",
"src:integration:wirespec",
"src:tools:generator",
"src:tools:router",
)
1 change: 1 addition & 0 deletions src/compiler/lib/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ kotlin {
dependencies {
implementation(project(":src:compiler:core"))
implementation(project(":src:converter:openapi"))
implementation(project(":src:tools:router"))
}
}
val jsMain by getting {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,45 +135,51 @@ private fun WsPrimitiveType.consume() =
WsPrimitiveType.Boolean -> Field.Reference.Primitive.Type.Boolean
}

fun Endpoint.produce() = WsEndpoint(
identifier = identifier.value,
comment = comment?.value,
method = method.produce(),
path = path.produce(),
query = query.produce(),
headers = headers.produce(),
cookies = cookies.produce(),
requests = requests.produce(),
responses = responses.produce(),
)

fun Node.produce(): WsNode =
when (this) {
is Type -> WsType(
identifier = identifier.value,
comment = comment?.value,
shape = shape.produce()
)
fun Type.produce() = WsType(
identifier = identifier.value,
comment = comment?.value,
shape = shape.produce()
)

is Endpoint -> WsEndpoint(
identifier = identifier.value,
comment = comment?.value,
method = method.produce(),
path = path.produce(),
query = query.produce(),
headers = headers.produce(),
cookies = cookies.produce(),
requests = requests.produce(),
responses = responses.produce(),
)
fun Enum.produce() = WsEnum(
identifier = identifier.value,
comment = comment?.value,
entries = entries.toTypedArray()
)

is Enum -> WsEnum(
identifier = identifier.value,
comment = comment?.value,
entries = entries.toTypedArray()
)
fun Refined.produce() = WsRefined(
identifier = identifier.value,
comment = comment?.value,
validator = validator.value
)

is Refined -> WsRefined(
identifier = identifier.value,
comment = comment?.value,
validator = validator.value
)
fun Union.produce() = WsUnion(
identifier = identifier.value,
comment = comment?.value,
entries = entries
.map { it.produce() }
.toTypedArray()
)

is Union -> WsUnion(
identifier = identifier.value,
comment = comment?.value,
entries = entries
.map { it.produce() }
.toTypedArray())
fun Node.produce(): WsNode =
when (this) {
is Type -> produce()
is Endpoint -> produce()
is Enum -> produce()
is Refined -> produce()
is Union -> produce()
}

fun List<Node>.produce(): Array<WsNode> =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
@file:OptIn(ExperimentalJsExport::class)

import community.flock.wirespec.compiler.core.parse.Endpoint
import community.flock.wirespec.compiler.lib.WsEndpoint
import community.flock.wirespec.compiler.lib.consume
import community.flock.wirespec.compiler.lib.produce
import community.flock.wirespec.router.MatchResult
import community.flock.wirespec.router.Route
import community.flock.wirespec.router.match

@JsExport
class WsRouter(
private val routes: Array<WsRoute>
){
fun match(method: String, path: String) = routes
.map { it.consume() }
.match(Endpoint.Method.valueOf(method), path)
?.produce()
}

@JsExport
data class WsMatchResult(
val endpoint: WsEndpoint,
val params: Map<String, String>,
val query: Map<String, String>
)

@JsExport
data class WsRoute(
val endpoint: WsEndpoint,
val regex: String,
val params: Array<String>
)

fun Route.produce() =
WsRoute(
endpoint = endpoint.produce(),
regex = regex.pattern,
params = params.toTypedArray()
)

fun WsRoute.consume() =
Route(
endpoint = endpoint.consume(),
regex = Regex(regex),
params = params.toList()
)

fun MatchResult.produce() =
WsMatchResult(
endpoint = endpoint.produce(),
params = params,
query = query
)

fun List<Route>.produce() = WsRouter(
routes = map { it.produce() }.toTypedArray()
)
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@ class TestLib {
@Test
fun testProduceConsume(){
val source = Resource("src/jsTest/resources/person.ws").readText()
println(source)
val res = WirespecSpec.parse(source)(noLogger)
res.map { ast ->
val output = ast.produce()
val input = output.map { it.consume() }
assertEquals(input, ast)
}
}

}
2 changes: 2 additions & 0 deletions src/plugin/npm/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,14 @@ kotlin {
implementation(project(":src:plugin:cli"))
implementation(project(":src:converter:openapi"))
implementation(project(":src:tools:generator"))
implementation(project(":src:tools:router"))
}
}
val jsMain by getting {
dependencies {
implementation(kotlin("test-annotations-common"))
implementation(kotlin("test-junit"))
implementation(libs.bundles.kotest)
implementation("com.goncalossilva:resources:0.4.0")
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
package community.flock.wirespec.plugin.npm

import WsMatchResult
import com.goncalossilva.resources.Resource
import community.flock.wirespec.compiler.core.WirespecSpec
import community.flock.wirespec.compiler.core.parse
import community.flock.wirespec.compiler.core.parse.Definition
import community.flock.wirespec.compiler.core.parse.Endpoint
import community.flock.wirespec.compiler.lib.WsEndpoint
import community.flock.wirespec.compiler.lib.WsLiteral
import community.flock.wirespec.compiler.lib.WsMethod
import community.flock.wirespec.compiler.lib.produce
import community.flock.wirespec.compiler.utils.noLogger
import io.kotest.matchers.collections.shouldHaveSize
import io.kotest.matchers.shouldBe
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.collections.contentEquals

class MainTest {

Expand All @@ -21,4 +30,30 @@ class MainTest {
assertEquals("""{"swagger":"2.0"""", openApiV2.first().result.substring(0, 16))
assertEquals("""{"openapi":"3.0.0"""", openApiV3.first().result.substring(0, 18))
}

@Test
fun testRouter() {
val file = Resource("src/commonTest/resources/person.ws").readText()
val ast = WirespecSpec.parse(file)(noLogger).getOrNull()
assertNotNull(ast)

val router = router(ast.produce())
val res = router.match("GET", "/todos")
val expected = WsMatchResult(
endpoint = ast
.filterIsInstance<Endpoint>()
.find { it.identifier.value == "GetTodos" }
?.produce()
?: error("Not found"),
params = mapOf(),
query = mapOf(),
)
res?.endpoint?.requests shouldBe expected.endpoint.requests
res?.endpoint?.responses?.shouldHaveSize(1)
res?.endpoint?.responses?.get(0)?.status shouldBe expected.endpoint.responses[0].status
res?.endpoint?.responses?.get(0)?.content shouldBe expected.endpoint.responses[0].content
res?.endpoint?.responses?.get(0)?.headers contentEquals expected.endpoint.responses[0].headers
res?.endpoint?.requests?.shouldHaveSize(1)
res?.endpoint?.requests?.get(0) shouldBe expected.endpoint.requests[0]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
package community.flock.wirespec.plugin.npm

import Generator.generate
import WsRouter
import community.flock.kotlinx.openapi.bindings.v2.SwaggerObject
import community.flock.kotlinx.openapi.bindings.v3.OpenAPIObject
import community.flock.wirespec.compiler.core.WirespecSpec
Expand All @@ -21,7 +22,10 @@ import community.flock.wirespec.compiler.utils.noLogger
import community.flock.wirespec.openapi.v2.OpenApiV2Emitter
import community.flock.wirespec.openapi.v3.OpenApiV3Emitter
import community.flock.wirespec.plugin.cli.main
import community.flock.wirespec.router.Route
import community.flock.wirespec.router.router
import kotlinx.serialization.json.Json
import produce

@JsExport
enum class Emitters {
Expand Down Expand Up @@ -76,3 +80,10 @@ fun emit(ast: Array<WsNode>, emitter: Emitters, packageName: String) = ast
}
.map { it.produce() }
.toTypedArray()

@JsExport
fun router(ast: Array<WsNode>): WsRouter = ast
.map { it.consume() }
.router()
.produce()

41 changes: 41 additions & 0 deletions src/tools/router/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
plugins {
kotlin("multiplatform")
}

group = "${libs.versions.group.id.get()}.router"
version = System.getenv(libs.versions.from.env.get()) ?: libs.versions.default.get()

repositories {
mavenCentral()
}

kotlin {
macosX64()
macosArm64()
linuxX64()
js(IR) {
nodejs()
}
jvm {
withJava()
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(libs.versions.java.get()))
}
}
}
sourceSets {
commonMain {
dependencies {
implementation(project(":src:compiler:core"))
}
}
commonTest {
dependencies {
implementation(kotlin("test-common"))
implementation(kotlin("test-annotations-common"))
implementation(kotlin("test-junit"))
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package community.flock.wirespec.router

import community.flock.wirespec.compiler.core.parse.AST
import community.flock.wirespec.compiler.core.parse.Endpoint
import community.flock.wirespec.compiler.core.parse.Endpoint.Segment

typealias Path = List<Segment>

data class MatchResult(
val endpoint: Endpoint,
val params: Map<String, String>,
val query: Map<String, String>
)

data class Route(
val endpoint: Endpoint,
val regex: Regex,
val params: List<String>
)

fun AST.router() = this
.filterIsInstance<Endpoint>()
.map { endpoint ->
Route(
endpoint = endpoint,
regex = endpoint.path.toRegex(),
params = endpoint.path.filterIsInstance<Segment.Param>().map { it.identifier.value }
)
}

fun Route.match(method: Endpoint.Method, path: String): MatchResult? {
val parts = path.split("?")
val p = parts[0]
val q = parts.getOrNull(1)
val match = regex.find(p)
println(regex)
return if (endpoint.method == method && match != null) {
MatchResult(
endpoint = endpoint,
params = params.associateWith { match.groups[it]?.value ?: error("parameter not found in matcher")},
query = q?.parseQuery().orEmpty()
)
} else {
null
}
}

fun List<Route>.match(method: Endpoint.Method, path: String): MatchResult? {
return firstNotNullOfOrNull { it.match(method, path) }
}

private fun String.parseQuery() = this
.split("&")
.map { it.split("=", limit = 2) }
.associate { (key, value) -> key to value }

fun Path.toRegex() = Regex(this.joinToString("/", "^/", "/*$") {
when (it) {
is Segment.Param -> """(?<${it.identifier.value}>[^/]+)"""
is Segment.Literal -> """(${it.value})"""
}
})
Loading