diff --git a/examples/Makefile b/examples/Makefile index 3cdba94d..d619fa5a 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -1,8 +1,8 @@ .PHONY: * build: - ./mvnw clean verify && \ - (cd gradle-ktor && ./gradlew clean build) && \ + ./mvnw clean test && \ + (cd gradle-ktor && ./gradlew clean test) && \ (cd npm-typescript && npm run build) clean: diff --git a/examples/README.md b/examples/README.md index f1ecf2c8..147cfd91 100644 --- a/examples/README.md +++ b/examples/README.md @@ -2,10 +2,11 @@ Here you can find examples of how to use: -* [A custom Emitter](maven-spring-custom/README.md) * [The Gradle Plugin](gradle-ktor/README.md) * [The Maven Plugin](maven-spring-compile/README.md) * [And convert an OpenAPI Specification](maven-spring-convert/README.md) +* [A custom Emitter](maven-spring-custom/README.md) +* [The Spring integration](maven-spring-integration/README.md) ## Integration diff --git a/examples/gradle-ktor/build.gradle.kts b/examples/gradle-ktor/build.gradle.kts index b75478a2..bc670311 100644 --- a/examples/gradle-ktor/build.gradle.kts +++ b/examples/gradle-ktor/build.gradle.kts @@ -36,7 +36,7 @@ dependencies { implementation(libs.bundles.ktor) implementation(libs.jackson) implementation(libs.logback) - testImplementation(kotlin("test")) + testImplementation(libs.kotlin.test) testImplementation(libs.bundles.ktor.test) } diff --git a/examples/gradle-ktor/gradle/libs.versions.toml b/examples/gradle-ktor/gradle/libs.versions.toml index aac849a8..d93fdf7e 100644 --- a/examples/gradle-ktor/gradle/libs.versions.toml +++ b/examples/gradle-ktor/gradle/libs.versions.toml @@ -8,6 +8,7 @@ logback = "1.4.14" kotlinx_serialization = "1.7.0" [libraries] +kotlin_test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } ktor_server_core = { module = "io.ktor:ktor-server-core-jvm", version.ref = "ktor" } ktor_server_netty = { module = "io.ktor:ktor-server-netty-jvm", version.ref = "ktor" } ktor_server_content_negotiation = { module = "io.ktor:ktor-server-content-negotiation", version.ref = "ktor" } diff --git a/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/gradle/app/user/User.kt b/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/gradle/app/user/User.kt deleted file mode 100644 index e6ab778a..00000000 --- a/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/gradle/app/user/User.kt +++ /dev/null @@ -1,3 +0,0 @@ -package community.flock.wirespec.example.gradle.app.user - -data class User(val name: String) diff --git a/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/gradle/app/Application.kt b/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/maven/custom/app/Application.kt similarity index 72% rename from examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/gradle/app/Application.kt rename to examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/maven/custom/app/Application.kt index db1b4239..9314336c 100644 --- a/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/gradle/app/Application.kt +++ b/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/maven/custom/app/Application.kt @@ -1,7 +1,7 @@ -package community.flock.wirespec.example.gradle.app +package community.flock.wirespec.example.maven.custom.app -import community.flock.wirespec.example.gradle.app.todo.LiveTodoRepository -import community.flock.wirespec.example.gradle.app.todo.todoModule +import community.flock.wirespec.example.maven.custom.app.todo.LiveTodoRepository +import community.flock.wirespec.example.maven.custom.app.todo.todoModule import io.ktor.serialization.kotlinx.json.json import io.ktor.server.application.Application import io.ktor.server.application.install diff --git a/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/gradle/app/common/Converter.kt b/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/maven/custom/app/common/Converter.kt similarity index 81% rename from examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/gradle/app/common/Converter.kt rename to examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/maven/custom/app/common/Converter.kt index 48907669..296b54ca 100644 --- a/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/gradle/app/common/Converter.kt +++ b/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/maven/custom/app/common/Converter.kt @@ -1,4 +1,4 @@ -package community.flock.wirespec.example.gradle.app.common +package community.flock.wirespec.example.maven.custom.app.common interface Converter : Internalizer, diff --git a/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/gradle/app/common/Transformer.kt b/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/maven/custom/app/common/Transformer.kt similarity index 71% rename from examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/gradle/app/common/Transformer.kt rename to examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/maven/custom/app/common/Transformer.kt index 3873a9b6..3c962a74 100644 --- a/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/gradle/app/common/Transformer.kt +++ b/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/maven/custom/app/common/Transformer.kt @@ -1,4 +1,4 @@ -package community.flock.wirespec.example.gradle.app.common +package community.flock.wirespec.example.maven.custom.app.common fun interface Consumer { fun DTO.consume(): DOMAIN diff --git a/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/gradle/app/common/Value.kt b/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/maven/custom/app/common/Value.kt similarity index 60% rename from examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/gradle/app/common/Value.kt rename to examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/maven/custom/app/common/Value.kt index 6fb6c2fa..c439b877 100644 --- a/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/gradle/app/common/Value.kt +++ b/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/maven/custom/app/common/Value.kt @@ -1,4 +1,4 @@ -package community.flock.wirespec.example.gradle.app.common +package community.flock.wirespec.example.maven.custom.app.common interface Value { val value: T diff --git a/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/gradle/app/common/Wirespec.kt b/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/maven/custom/app/common/Wirespec.kt similarity index 96% rename from examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/gradle/app/common/Wirespec.kt rename to examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/maven/custom/app/common/Wirespec.kt index fc597dd1..902b857e 100644 --- a/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/gradle/app/common/Wirespec.kt +++ b/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/maven/custom/app/common/Wirespec.kt @@ -1,4 +1,4 @@ -package community.flock.wirespec.example.gradle.app.common +package community.flock.wirespec.example.maven.custom.app.common import community.flock.wirespec.kotlin.Wirespec import io.ktor.client.HttpClient diff --git a/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/gradle/app/exception/AppException.kt b/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/maven/custom/app/exception/AppException.kt similarity index 72% rename from examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/gradle/app/exception/AppException.kt rename to examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/maven/custom/app/exception/AppException.kt index b79aa6cd..b14faa87 100644 --- a/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/gradle/app/exception/AppException.kt +++ b/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/maven/custom/app/exception/AppException.kt @@ -1,6 +1,6 @@ -package community.flock.wirespec.example.gradle.app.exception +package community.flock.wirespec.example.maven.custom.app.exception -import community.flock.wirespec.example.gradle.app.todo.Todo +import community.flock.wirespec.example.maven.custom.app.todo.Todo sealed class AppException(override val message: String) : RuntimeException(message) diff --git a/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/gradle/app/exception/ExceptionHandler.kt b/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/maven/custom/app/exception/ExceptionHandler.kt similarity index 84% rename from examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/gradle/app/exception/ExceptionHandler.kt rename to examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/maven/custom/app/exception/ExceptionHandler.kt index 632449e9..5a2a72f3 100644 --- a/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/gradle/app/exception/ExceptionHandler.kt +++ b/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/maven/custom/app/exception/ExceptionHandler.kt @@ -1,4 +1,4 @@ -package community.flock.wirespec.example.gradle.app.exception +package community.flock.wirespec.example.maven.custom.app.exception import community.flock.wirespec.generated.kotlin.Error diff --git a/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/gradle/app/todo/LiveTodoRepository.kt b/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/maven/custom/app/todo/LiveTodoRepository.kt similarity index 79% rename from examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/gradle/app/todo/LiveTodoRepository.kt rename to examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/maven/custom/app/todo/LiveTodoRepository.kt index 96665c9d..78445974 100644 --- a/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/gradle/app/todo/LiveTodoRepository.kt +++ b/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/maven/custom/app/todo/LiveTodoRepository.kt @@ -1,6 +1,6 @@ -package community.flock.wirespec.example.gradle.app.todo +package community.flock.wirespec.example.maven.custom.app.todo -import community.flock.wirespec.example.gradle.app.exception.TodoNotFoundException +import community.flock.wirespec.example.maven.custom.app.exception.TodoNotFoundException class LiveTodoRepository : TodoRepository { diff --git a/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/gradle/app/todo/Todo.kt b/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/maven/custom/app/todo/Todo.kt similarity index 63% rename from examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/gradle/app/todo/Todo.kt rename to examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/maven/custom/app/todo/Todo.kt index 9788aab1..5fe37907 100644 --- a/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/gradle/app/todo/Todo.kt +++ b/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/maven/custom/app/todo/Todo.kt @@ -1,6 +1,6 @@ -package community.flock.wirespec.example.gradle.app.todo +package community.flock.wirespec.example.maven.custom.app.todo -import community.flock.wirespec.example.gradle.app.common.Value +import community.flock.wirespec.example.maven.custom.app.common.Value data class Todo( val id: Id, diff --git a/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/gradle/app/todo/TodoHandler.kt b/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/maven/custom/app/todo/TodoHandler.kt similarity index 79% rename from examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/gradle/app/todo/TodoHandler.kt rename to examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/maven/custom/app/todo/TodoHandler.kt index d44f686a..d311f6bf 100644 --- a/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/gradle/app/todo/TodoHandler.kt +++ b/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/maven/custom/app/todo/TodoHandler.kt @@ -1,7 +1,8 @@ -package community.flock.wirespec.example.gradle.app.todo +package community.flock.wirespec.example.maven.custom.app.todo -import community.flock.wirespec.example.gradle.app.todo.TodoConsumer.consume -import community.flock.wirespec.example.gradle.app.todo.TodoProducer.produce +import community.flock.wirespec.example.maven.custom.app.exception.TodoIdNotValidException +import community.flock.wirespec.example.maven.custom.app.todo.TodoConsumer.consume +import community.flock.wirespec.example.maven.custom.app.todo.TodoProducer.produce import community.flock.wirespec.generated.kotlin.DeleteTodoByIdEndpoint import community.flock.wirespec.generated.kotlin.GetTodoByIdEndpoint import community.flock.wirespec.generated.kotlin.GetTodosEndpoint @@ -27,7 +28,7 @@ class TodoHandler(liveTodoRepository: TodoRepository) : TodoApi { override suspend fun getTodoById(request: GetTodoByIdEndpoint.Request): GetTodoByIdEndpoint.Response200 = request.path.id - .also { if (!it.validate()) throw community.flock.wirespec.example.gradle.app.exception.TodoIdNotValidException( + .also { if (!it.validate()) throw TodoIdNotValidException( it.value ) } @@ -46,7 +47,7 @@ class TodoHandler(liveTodoRepository: TodoRepository) : TodoApi { override suspend fun deleteTodoById(request: DeleteTodoByIdEndpoint.Request): DeleteTodoByIdEndpoint.Response200 = request.path.id - .also { if (!it.validate()) throw community.flock.wirespec.example.gradle.app.exception.TodoIdNotValidException( + .also { if (!it.validate()) throw TodoIdNotValidException( it.value ) } diff --git a/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/gradle/app/todo/TodoRepository.kt b/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/maven/custom/app/todo/TodoRepository.kt similarity index 80% rename from examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/gradle/app/todo/TodoRepository.kt rename to examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/maven/custom/app/todo/TodoRepository.kt index db7c97a2..2af7138d 100644 --- a/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/gradle/app/todo/TodoRepository.kt +++ b/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/maven/custom/app/todo/TodoRepository.kt @@ -1,4 +1,4 @@ -package community.flock.wirespec.example.gradle.app.todo +package community.flock.wirespec.example.maven.custom.app.todo interface HasTodoRepository { val todoRepository: TodoRepository diff --git a/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/gradle/app/todo/TodoRouting.kt b/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/maven/custom/app/todo/TodoRouting.kt similarity index 95% rename from examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/gradle/app/todo/TodoRouting.kt rename to examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/maven/custom/app/todo/TodoRouting.kt index de1dbde4..a2451022 100644 --- a/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/gradle/app/todo/TodoRouting.kt +++ b/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/maven/custom/app/todo/TodoRouting.kt @@ -1,6 +1,6 @@ -package community.flock.wirespec.example.gradle.app.todo +package community.flock.wirespec.example.maven.custom.app.todo -import community.flock.wirespec.example.gradle.app.common.Serialization +import community.flock.wirespec.example.maven.custom.app.common.Serialization import community.flock.wirespec.generated.kotlin.DeleteTodoByIdEndpoint import community.flock.wirespec.generated.kotlin.GetTodoByIdEndpoint import community.flock.wirespec.generated.kotlin.GetTodosEndpoint diff --git a/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/gradle/app/todo/TodoService.kt b/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/maven/custom/app/todo/TodoService.kt similarity index 84% rename from examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/gradle/app/todo/TodoService.kt rename to examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/maven/custom/app/todo/TodoService.kt index 1f18e1b5..f8e71932 100644 --- a/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/gradle/app/todo/TodoService.kt +++ b/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/maven/custom/app/todo/TodoService.kt @@ -1,4 +1,4 @@ -package community.flock.wirespec.example.gradle.app.todo +package community.flock.wirespec.example.maven.custom.app.todo interface TodoService : HasTodoRepository diff --git a/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/gradle/app/todo/TodoTransformer.kt b/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/maven/custom/app/todo/TodoTransformer.kt similarity index 74% rename from examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/gradle/app/todo/TodoTransformer.kt rename to examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/maven/custom/app/todo/TodoTransformer.kt index 9c97bbd7..d2ac547d 100644 --- a/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/gradle/app/todo/TodoTransformer.kt +++ b/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/maven/custom/app/todo/TodoTransformer.kt @@ -1,7 +1,7 @@ -package community.flock.wirespec.example.gradle.app.todo +package community.flock.wirespec.example.maven.custom.app.todo -import community.flock.wirespec.example.gradle.app.common.Consumer -import community.flock.wirespec.example.gradle.app.common.Producer +import community.flock.wirespec.example.maven.custom.app.common.Consumer +import community.flock.wirespec.example.maven.custom.app.common.Producer import community.flock.wirespec.generated.kotlin.PotentialTodoDto import community.flock.wirespec.generated.kotlin.TodoDto import community.flock.wirespec.generated.kotlin.TodoId diff --git a/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/gradle/app/user/LiveUserAdapter.kt b/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/maven/custom/app/user/LiveUserAdapter.kt similarity index 91% rename from examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/gradle/app/user/LiveUserAdapter.kt rename to examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/maven/custom/app/user/LiveUserAdapter.kt index 939d0c8e..5bc5f38f 100644 --- a/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/gradle/app/user/LiveUserAdapter.kt +++ b/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/maven/custom/app/user/LiveUserAdapter.kt @@ -1,6 +1,6 @@ -package community.flock.wirespec.example.gradle.app.user +package community.flock.wirespec.example.maven.custom.app.user -import community.flock.wirespec.example.gradle.app.user.UserConverter.internalize +import community.flock.wirespec.example.maven.custom.app.user.UserConverter.internalize import community.flock.wirespec.generated.kotlin.DeleteUserByNameEndpoint import community.flock.wirespec.generated.kotlin.GetUserByNameEndpoint import community.flock.wirespec.generated.kotlin.GetUsersEndpoint diff --git a/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/gradle/app/user/LiveUserClient.kt b/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/maven/custom/app/user/LiveUserClient.kt similarity index 86% rename from examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/gradle/app/user/LiveUserClient.kt rename to examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/maven/custom/app/user/LiveUserClient.kt index 90f5b9c2..3c10c562 100644 --- a/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/gradle/app/user/LiveUserClient.kt +++ b/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/maven/custom/app/user/LiveUserClient.kt @@ -1,7 +1,7 @@ -package community.flock.wirespec.example.gradle.app.user +package community.flock.wirespec.example.maven.custom.app.user -import community.flock.wirespec.example.gradle.app.common.Serialization -import community.flock.wirespec.example.gradle.app.common.WirespecClient +import community.flock.wirespec.example.maven.custom.app.common.Serialization +import community.flock.wirespec.example.maven.custom.app.common.WirespecClient import community.flock.wirespec.generated.kotlin.DeleteUserByNameEndpoint import community.flock.wirespec.generated.kotlin.GetUserByNameEndpoint import community.flock.wirespec.generated.kotlin.GetUsersEndpoint diff --git a/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/maven/custom/app/user/User.kt b/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/maven/custom/app/user/User.kt new file mode 100644 index 00000000..1c9c2e0c --- /dev/null +++ b/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/maven/custom/app/user/User.kt @@ -0,0 +1,3 @@ +package community.flock.wirespec.example.maven.custom.app.user + +data class User(val name: String) diff --git a/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/gradle/app/user/UserAdapter.kt b/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/maven/custom/app/user/UserAdapter.kt similarity index 79% rename from examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/gradle/app/user/UserAdapter.kt rename to examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/maven/custom/app/user/UserAdapter.kt index 061aeda3..b2d37565 100644 --- a/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/gradle/app/user/UserAdapter.kt +++ b/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/maven/custom/app/user/UserAdapter.kt @@ -1,4 +1,4 @@ -package community.flock.wirespec.example.gradle.app.user +package community.flock.wirespec.example.maven.custom.app.user interface HasUserAdapter { val userAdapter: UserAdapter diff --git a/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/gradle/app/user/UserConverter.kt b/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/maven/custom/app/user/UserConverter.kt similarity index 62% rename from examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/gradle/app/user/UserConverter.kt rename to examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/maven/custom/app/user/UserConverter.kt index 812e05ab..e9551cb7 100644 --- a/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/gradle/app/user/UserConverter.kt +++ b/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/maven/custom/app/user/UserConverter.kt @@ -1,6 +1,6 @@ -package community.flock.wirespec.example.gradle.app.user +package community.flock.wirespec.example.maven.custom.app.user -import community.flock.wirespec.example.gradle.app.common.Converter +import community.flock.wirespec.example.maven.custom.app.common.Converter import community.flock.wirespec.generated.kotlin.UserDto object UserConverter : Converter { diff --git a/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/gradle/app/user/UserService.kt b/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/maven/custom/app/user/UserService.kt similarity index 83% rename from examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/gradle/app/user/UserService.kt rename to examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/maven/custom/app/user/UserService.kt index 97ba873b..1979883f 100644 --- a/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/gradle/app/user/UserService.kt +++ b/examples/gradle-ktor/src/main/kotlin/community/flock/wirespec/example/maven/custom/app/user/UserService.kt @@ -1,4 +1,4 @@ -package community.flock.wirespec.example.gradle.app.user +package community.flock.wirespec.example.maven.custom.app.user interface UserService : HasUserAdapter diff --git a/examples/gradle-ktor/src/test/kotlin/community/flock/wirespec/example/gradle/app/ApplicationTest.kt b/examples/gradle-ktor/src/test/kotlin/community/flock/wirespec/example/maven/custom/app/ApplicationTest.kt similarity index 75% rename from examples/gradle-ktor/src/test/kotlin/community/flock/wirespec/example/gradle/app/ApplicationTest.kt rename to examples/gradle-ktor/src/test/kotlin/community/flock/wirespec/example/maven/custom/app/ApplicationTest.kt index 522586cd..256eca90 100644 --- a/examples/gradle-ktor/src/test/kotlin/community/flock/wirespec/example/gradle/app/ApplicationTest.kt +++ b/examples/gradle-ktor/src/test/kotlin/community/flock/wirespec/example/maven/custom/app/ApplicationTest.kt @@ -1,7 +1,7 @@ -package community.flock.wirespec.example.gradle.app +package community.flock.wirespec.example.maven.custom.app -import community.flock.wirespec.example.gradle.app.todo.LiveTodoRepository -import community.flock.wirespec.example.gradle.app.todo.todoModule +import community.flock.wirespec.example.maven.custom.app.todo.LiveTodoRepository +import community.flock.wirespec.example.maven.custom.app.todo.todoModule import io.ktor.client.request.get import io.ktor.client.statement.bodyAsText import io.ktor.http.HttpStatusCode diff --git a/examples/gradle-ktor/src/test/kotlin/community/flock/wirespec/example/gradle/app/todo/TodoTest.kt b/examples/gradle-ktor/src/test/kotlin/community/flock/wirespec/example/maven/custom/app/todo/TodoTest.kt similarity index 88% rename from examples/gradle-ktor/src/test/kotlin/community/flock/wirespec/example/gradle/app/todo/TodoTest.kt rename to examples/gradle-ktor/src/test/kotlin/community/flock/wirespec/example/maven/custom/app/todo/TodoTest.kt index 653d1d5f..8a1d501c 100644 --- a/examples/gradle-ktor/src/test/kotlin/community/flock/wirespec/example/gradle/app/todo/TodoTest.kt +++ b/examples/gradle-ktor/src/test/kotlin/community/flock/wirespec/example/maven/custom/app/todo/TodoTest.kt @@ -1,4 +1,4 @@ -package community.flock.wirespec.example.gradle.app.todo +package community.flock.wirespec.example.maven.custom.app.todo import org.junit.Test import kotlin.test.assertTrue diff --git a/examples/gradle-ktor/src/test/kotlin/community/flock/wirespec/example/gradle/app/user/TestUserClient.kt b/examples/gradle-ktor/src/test/kotlin/community/flock/wirespec/example/maven/custom/app/user/TestUserClient.kt similarity index 95% rename from examples/gradle-ktor/src/test/kotlin/community/flock/wirespec/example/gradle/app/user/TestUserClient.kt rename to examples/gradle-ktor/src/test/kotlin/community/flock/wirespec/example/maven/custom/app/user/TestUserClient.kt index 14484558..9d32881d 100644 --- a/examples/gradle-ktor/src/test/kotlin/community/flock/wirespec/example/gradle/app/user/TestUserClient.kt +++ b/examples/gradle-ktor/src/test/kotlin/community/flock/wirespec/example/maven/custom/app/user/TestUserClient.kt @@ -1,4 +1,4 @@ -package community.flock.wirespec.example.gradle.app.user +package community.flock.wirespec.example.maven.custom.app.user import community.flock.wirespec.generated.kotlin.DeleteUserByNameEndpoint import community.flock.wirespec.generated.kotlin.GetUserByNameEndpoint diff --git a/examples/gradle-ktor/src/test/kotlin/community/flock/wirespec/example/gradle/app/user/UserTest.kt b/examples/gradle-ktor/src/test/kotlin/community/flock/wirespec/example/maven/custom/app/user/UserTest.kt similarity index 94% rename from examples/gradle-ktor/src/test/kotlin/community/flock/wirespec/example/gradle/app/user/UserTest.kt rename to examples/gradle-ktor/src/test/kotlin/community/flock/wirespec/example/maven/custom/app/user/UserTest.kt index 2532431b..24066b72 100644 --- a/examples/gradle-ktor/src/test/kotlin/community/flock/wirespec/example/gradle/app/user/UserTest.kt +++ b/examples/gradle-ktor/src/test/kotlin/community/flock/wirespec/example/maven/custom/app/user/UserTest.kt @@ -1,4 +1,4 @@ -package community.flock.wirespec.example.gradle.app.user +package community.flock.wirespec.example.maven.custom.app.user import org.junit.Test import kotlin.test.assertEquals diff --git a/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/gradle/app/user/User.java b/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/gradle/app/user/User.java deleted file mode 100644 index 11d15588..00000000 --- a/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/gradle/app/user/User.java +++ /dev/null @@ -1,4 +0,0 @@ -package community.flock.wirespec.example.gradle.app.user; - -public record User(String name) { -} diff --git a/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/gradle/app/Application.java b/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/maven/custom/app/Application.java similarity index 82% rename from examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/gradle/app/Application.java rename to examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/maven/custom/app/Application.java index 07016831..3830a99c 100644 --- a/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/gradle/app/Application.java +++ b/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/maven/custom/app/Application.java @@ -1,4 +1,4 @@ -package community.flock.wirespec.example.gradle.app; +package community.flock.wirespec.example.maven.custom.app; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; diff --git a/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/gradle/app/common/Converter.java b/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/maven/custom/app/common/Converter.java similarity index 56% rename from examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/gradle/app/common/Converter.java rename to examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/maven/custom/app/common/Converter.java index 4257157e..4b625761 100644 --- a/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/gradle/app/common/Converter.java +++ b/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/maven/custom/app/common/Converter.java @@ -1,4 +1,4 @@ -package community.flock.wirespec.example.gradle.app.common; +package community.flock.wirespec.example.maven.custom.app.common; public interface Converter extends Internalizer, Externalizer { } diff --git a/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/gradle/app/common/Externalizer.java b/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/maven/custom/app/common/Externalizer.java similarity index 51% rename from examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/gradle/app/common/Externalizer.java rename to examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/maven/custom/app/common/Externalizer.java index 8885dc85..cc125776 100644 --- a/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/gradle/app/common/Externalizer.java +++ b/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/maven/custom/app/common/Externalizer.java @@ -1,4 +1,4 @@ -package community.flock.wirespec.example.gradle.app.common; +package community.flock.wirespec.example.maven.custom.app.common; public interface Externalizer { E externalize(I domain); diff --git a/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/gradle/app/common/Internalizer.java b/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/maven/custom/app/common/Internalizer.java similarity index 75% rename from examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/gradle/app/common/Internalizer.java rename to examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/maven/custom/app/common/Internalizer.java index 31112715..64b6f865 100644 --- a/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/gradle/app/common/Internalizer.java +++ b/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/maven/custom/app/common/Internalizer.java @@ -1,4 +1,4 @@ -package community.flock.wirespec.example.gradle.app.common; +package community.flock.wirespec.example.maven.custom.app.common; import java.util.List; diff --git a/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/gradle/app/common/WirespecSerializer.java b/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/maven/custom/app/common/WirespecSerializer.java similarity index 86% rename from examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/gradle/app/common/WirespecSerializer.java rename to examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/maven/custom/app/common/WirespecSerializer.java index 91b51b1c..2f41892c 100644 --- a/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/gradle/app/common/WirespecSerializer.java +++ b/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/maven/custom/app/common/WirespecSerializer.java @@ -1,8 +1,8 @@ -package community.flock.wirespec.example.gradle.app.common; +package community.flock.wirespec.example.maven.custom.app.common; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import community.flock.wirespec.example.gradle.app.exception.SerializationException; +import community.flock.wirespec.example.maven.custom.app.exception.SerializationException; import community.flock.wirespec.java.Wirespec; import org.springframework.stereotype.Component; diff --git a/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/gradle/app/common/WirespecTransporter.java b/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/maven/custom/app/common/WirespecTransporter.java similarity index 96% rename from examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/gradle/app/common/WirespecTransporter.java rename to examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/maven/custom/app/common/WirespecTransporter.java index c4b1376d..175c0c57 100644 --- a/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/gradle/app/common/WirespecTransporter.java +++ b/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/maven/custom/app/common/WirespecTransporter.java @@ -1,4 +1,4 @@ -package community.flock.wirespec.example.gradle.app.common; +package community.flock.wirespec.example.maven.custom.app.common; import community.flock.wirespec.java.Wirespec.RawRequest; import community.flock.wirespec.java.Wirespec.RawResponse; diff --git a/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/gradle/app/config/AppConfiguration.java b/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/maven/custom/app/config/AppConfiguration.java similarity index 87% rename from examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/gradle/app/config/AppConfiguration.java rename to examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/maven/custom/app/config/AppConfiguration.java index 5eef84c2..82dee00c 100644 --- a/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/gradle/app/config/AppConfiguration.java +++ b/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/maven/custom/app/config/AppConfiguration.java @@ -1,4 +1,4 @@ -package community.flock.wirespec.example.gradle.app.config; +package community.flock.wirespec.example.maven.custom.app.config; import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.context.annotation.Bean; diff --git a/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/gradle/app/exception/AppException.java b/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/maven/custom/app/exception/AppException.java similarity index 81% rename from examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/gradle/app/exception/AppException.java rename to examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/maven/custom/app/exception/AppException.java index 5bc1e3f4..7e288d7f 100644 --- a/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/gradle/app/exception/AppException.java +++ b/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/maven/custom/app/exception/AppException.java @@ -1,4 +1,4 @@ -package community.flock.wirespec.example.gradle.app.exception; +package community.flock.wirespec.example.maven.custom.app.exception; public sealed class AppException extends RuntimeException permits Conflict, NotFound, CallInterrupted, SerializationException { public AppException(String message) { diff --git a/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/gradle/app/exception/CallInterrupted.java b/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/maven/custom/app/exception/CallInterrupted.java similarity index 68% rename from examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/gradle/app/exception/CallInterrupted.java rename to examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/maven/custom/app/exception/CallInterrupted.java index da215125..d0c678c9 100644 --- a/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/gradle/app/exception/CallInterrupted.java +++ b/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/maven/custom/app/exception/CallInterrupted.java @@ -1,4 +1,4 @@ -package community.flock.wirespec.example.gradle.app.exception; +package community.flock.wirespec.example.maven.custom.app.exception; public final class CallInterrupted extends AppException { public CallInterrupted(Exception e) { diff --git a/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/gradle/app/exception/Conflict.java b/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/maven/custom/app/exception/Conflict.java similarity index 79% rename from examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/gradle/app/exception/Conflict.java rename to examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/maven/custom/app/exception/Conflict.java index 1f2d9045..0741aa01 100644 --- a/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/gradle/app/exception/Conflict.java +++ b/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/maven/custom/app/exception/Conflict.java @@ -1,4 +1,4 @@ -package community.flock.wirespec.example.gradle.app.exception; +package community.flock.wirespec.example.maven.custom.app.exception; public sealed class Conflict extends AppException { public Conflict(String message) { diff --git a/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/gradle/app/exception/NotFound.java b/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/maven/custom/app/exception/NotFound.java similarity index 78% rename from examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/gradle/app/exception/NotFound.java rename to examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/maven/custom/app/exception/NotFound.java index 158302cc..84cf842b 100644 --- a/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/gradle/app/exception/NotFound.java +++ b/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/maven/custom/app/exception/NotFound.java @@ -1,4 +1,4 @@ -package community.flock.wirespec.example.gradle.app.exception; +package community.flock.wirespec.example.maven.custom.app.exception; public sealed class NotFound extends AppException { public NotFound(String message) { diff --git a/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/gradle/app/exception/SerializationException.java b/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/maven/custom/app/exception/SerializationException.java similarity index 71% rename from examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/gradle/app/exception/SerializationException.java rename to examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/maven/custom/app/exception/SerializationException.java index 84e1ed7c..089216f8 100644 --- a/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/gradle/app/exception/SerializationException.java +++ b/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/maven/custom/app/exception/SerializationException.java @@ -1,4 +1,4 @@ -package community.flock.wirespec.example.gradle.app.exception; +package community.flock.wirespec.example.maven.custom.app.exception; public final class SerializationException extends AppException { public SerializationException(Exception cause) { diff --git a/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/gradle/app/todo/LiveTodoRepository.java b/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/maven/custom/app/todo/LiveTodoRepository.java similarity index 95% rename from examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/gradle/app/todo/LiveTodoRepository.java rename to examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/maven/custom/app/todo/LiveTodoRepository.java index 481e89d4..82d3b4f4 100644 --- a/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/gradle/app/todo/LiveTodoRepository.java +++ b/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/maven/custom/app/todo/LiveTodoRepository.java @@ -1,4 +1,4 @@ -package community.flock.wirespec.example.gradle.app.todo; +package community.flock.wirespec.example.maven.custom.app.todo; import community.flock.wirespec.generated.java.Todo; import community.flock.wirespec.generated.java.TodoId; diff --git a/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/gradle/app/todo/TodoController.java b/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/maven/custom/app/todo/TodoController.java similarity index 93% rename from examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/gradle/app/todo/TodoController.java rename to examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/maven/custom/app/todo/TodoController.java index 52f03314..642db7f1 100644 --- a/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/gradle/app/todo/TodoController.java +++ b/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/maven/custom/app/todo/TodoController.java @@ -1,6 +1,6 @@ -package community.flock.wirespec.example.gradle.app.todo; +package community.flock.wirespec.example.maven.custom.app.todo; -import community.flock.wirespec.example.gradle.app.exception.NotFound; +import community.flock.wirespec.example.maven.custom.app.exception.NotFound; import community.flock.wirespec.generated.java.Error; import community.flock.wirespec.generated.java.Todo; import community.flock.wirespec.generated.java.TodoId; diff --git a/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/gradle/app/todo/TodoRepository.java b/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/maven/custom/app/todo/TodoRepository.java similarity index 83% rename from examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/gradle/app/todo/TodoRepository.java rename to examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/maven/custom/app/todo/TodoRepository.java index aaf54c44..8b607eb0 100644 --- a/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/gradle/app/todo/TodoRepository.java +++ b/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/maven/custom/app/todo/TodoRepository.java @@ -1,4 +1,4 @@ -package community.flock.wirespec.example.gradle.app.todo; +package community.flock.wirespec.example.maven.custom.app.todo; import community.flock.wirespec.generated.java.Todo; import community.flock.wirespec.generated.java.TodoId; diff --git a/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/gradle/app/todo/TodoService.java b/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/maven/custom/app/todo/TodoService.java similarity index 92% rename from examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/gradle/app/todo/TodoService.java rename to examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/maven/custom/app/todo/TodoService.java index 4ba9ad9c..c60a54ba 100644 --- a/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/gradle/app/todo/TodoService.java +++ b/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/maven/custom/app/todo/TodoService.java @@ -1,4 +1,4 @@ -package community.flock.wirespec.example.gradle.app.todo; +package community.flock.wirespec.example.maven.custom.app.todo; import community.flock.wirespec.generated.java.Todo; import community.flock.wirespec.generated.java.TodoId; diff --git a/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/gradle/app/user/LiveUserAdapter.java b/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/maven/custom/app/user/LiveUserAdapter.java similarity index 89% rename from examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/gradle/app/user/LiveUserAdapter.java rename to examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/maven/custom/app/user/LiveUserAdapter.java index 8e6f0a75..92d97e44 100644 --- a/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/gradle/app/user/LiveUserAdapter.java +++ b/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/maven/custom/app/user/LiveUserAdapter.java @@ -1,8 +1,8 @@ -package community.flock.wirespec.example.gradle.app.user; +package community.flock.wirespec.example.maven.custom.app.user; -import community.flock.wirespec.example.gradle.app.exception.CallInterrupted; -import community.flock.wirespec.example.gradle.app.exception.Conflict; -import community.flock.wirespec.example.gradle.app.exception.NotFound; +import community.flock.wirespec.example.maven.custom.app.exception.CallInterrupted; +import community.flock.wirespec.example.maven.custom.app.exception.Conflict; +import community.flock.wirespec.example.maven.custom.app.exception.NotFound; import community.flock.wirespec.generated.java.DeleteUserByNameEndpoint; import community.flock.wirespec.generated.java.GetUserByNameEndpoint; import community.flock.wirespec.generated.java.GetUsersEndpoint; diff --git a/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/gradle/app/user/LiveUserClient.java b/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/maven/custom/app/user/LiveUserClient.java similarity index 91% rename from examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/gradle/app/user/LiveUserClient.java rename to examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/maven/custom/app/user/LiveUserClient.java index a56688c6..7105fbd0 100644 --- a/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/gradle/app/user/LiveUserClient.java +++ b/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/maven/custom/app/user/LiveUserClient.java @@ -1,7 +1,7 @@ -package community.flock.wirespec.example.gradle.app.user; +package community.flock.wirespec.example.maven.custom.app.user; -import community.flock.wirespec.example.gradle.app.common.WirespecSerializer; -import community.flock.wirespec.example.gradle.app.common.WirespecTransporter; +import community.flock.wirespec.example.maven.custom.app.common.WirespecSerializer; +import community.flock.wirespec.example.maven.custom.app.common.WirespecTransporter; import community.flock.wirespec.generated.java.DeleteUserByNameEndpoint; import community.flock.wirespec.generated.java.GetUserByNameEndpoint; import community.flock.wirespec.generated.java.GetUsersEndpoint; diff --git a/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/maven/custom/app/user/User.java b/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/maven/custom/app/user/User.java new file mode 100644 index 00000000..7e1f4756 --- /dev/null +++ b/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/maven/custom/app/user/User.java @@ -0,0 +1,4 @@ +package community.flock.wirespec.example.maven.custom.app.user; + +public record User(String name) { +} diff --git a/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/gradle/app/user/UserAdapter.java b/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/maven/custom/app/user/UserAdapter.java similarity index 82% rename from examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/gradle/app/user/UserAdapter.java rename to examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/maven/custom/app/user/UserAdapter.java index 09b76f5c..b173d044 100644 --- a/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/gradle/app/user/UserAdapter.java +++ b/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/maven/custom/app/user/UserAdapter.java @@ -1,4 +1,4 @@ -package community.flock.wirespec.example.gradle.app.user; +package community.flock.wirespec.example.maven.custom.app.user; import java.util.List; diff --git a/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/gradle/app/user/UserClient.java b/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/maven/custom/app/user/UserClient.java similarity index 88% rename from examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/gradle/app/user/UserClient.java rename to examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/maven/custom/app/user/UserClient.java index 74363666..06a5d78e 100644 --- a/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/gradle/app/user/UserClient.java +++ b/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/maven/custom/app/user/UserClient.java @@ -1,4 +1,4 @@ -package community.flock.wirespec.example.gradle.app.user; +package community.flock.wirespec.example.maven.custom.app.user; import community.flock.wirespec.generated.java.DeleteUserByNameEndpoint; import community.flock.wirespec.generated.java.GetUserByNameEndpoint; diff --git a/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/gradle/app/user/UserContext.java b/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/maven/custom/app/user/UserContext.java similarity index 92% rename from examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/gradle/app/user/UserContext.java rename to examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/maven/custom/app/user/UserContext.java index 4daac02f..cd86ce41 100644 --- a/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/gradle/app/user/UserContext.java +++ b/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/maven/custom/app/user/UserContext.java @@ -1,4 +1,4 @@ -package community.flock.wirespec.example.gradle.app.user; +package community.flock.wirespec.example.maven.custom.app.user; import java.util.List; diff --git a/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/gradle/app/user/UserConverter.java b/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/maven/custom/app/user/UserConverter.java similarity index 74% rename from examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/gradle/app/user/UserConverter.java rename to examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/maven/custom/app/user/UserConverter.java index dba26d29..a24b7c2c 100644 --- a/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/gradle/app/user/UserConverter.java +++ b/examples/maven-spring-compile/src/main/java/community/flock/wirespec/example/maven/custom/app/user/UserConverter.java @@ -1,6 +1,6 @@ -package community.flock.wirespec.example.gradle.app.user; +package community.flock.wirespec.example.maven.custom.app.user; -import community.flock.wirespec.example.gradle.app.common.Converter; +import community.flock.wirespec.example.maven.custom.app.common.Converter; import community.flock.wirespec.generated.java.UserDto; import org.springframework.stereotype.Component; diff --git a/examples/maven-spring-compile/src/test/java/community/flock/wirespec/example/gradle/app/todo/TodoTest.java b/examples/maven-spring-compile/src/test/java/community/flock/wirespec/example/maven/custom/app/todo/TodoTest.java similarity index 87% rename from examples/maven-spring-compile/src/test/java/community/flock/wirespec/example/gradle/app/todo/TodoTest.java rename to examples/maven-spring-compile/src/test/java/community/flock/wirespec/example/maven/custom/app/todo/TodoTest.java index 98d1f2f9..ae0114b9 100644 --- a/examples/maven-spring-compile/src/test/java/community/flock/wirespec/example/gradle/app/todo/TodoTest.java +++ b/examples/maven-spring-compile/src/test/java/community/flock/wirespec/example/maven/custom/app/todo/TodoTest.java @@ -1,4 +1,4 @@ -package community.flock.wirespec.example.gradle.app.todo; +package community.flock.wirespec.example.maven.custom.app.todo; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; diff --git a/examples/maven-spring-compile/src/test/java/community/flock/wirespec/example/gradle/app/user/TestUserClient.java b/examples/maven-spring-compile/src/test/java/community/flock/wirespec/example/maven/custom/app/user/TestUserClient.java similarity index 97% rename from examples/maven-spring-compile/src/test/java/community/flock/wirespec/example/gradle/app/user/TestUserClient.java rename to examples/maven-spring-compile/src/test/java/community/flock/wirespec/example/maven/custom/app/user/TestUserClient.java index f8522c87..63724863 100644 --- a/examples/maven-spring-compile/src/test/java/community/flock/wirespec/example/gradle/app/user/TestUserClient.java +++ b/examples/maven-spring-compile/src/test/java/community/flock/wirespec/example/maven/custom/app/user/TestUserClient.java @@ -1,4 +1,4 @@ -package community.flock.wirespec.example.gradle.app.user; +package community.flock.wirespec.example.maven.custom.app.user; import community.flock.wirespec.generated.java.DeleteUserByNameEndpoint; import community.flock.wirespec.generated.java.GetUserByNameEndpoint; diff --git a/examples/maven-spring-compile/src/test/java/community/flock/wirespec/example/gradle/app/user/UserTest.java b/examples/maven-spring-compile/src/test/java/community/flock/wirespec/example/maven/custom/app/user/UserTest.java similarity index 72% rename from examples/maven-spring-compile/src/test/java/community/flock/wirespec/example/gradle/app/user/UserTest.java rename to examples/maven-spring-compile/src/test/java/community/flock/wirespec/example/maven/custom/app/user/UserTest.java index 168824a8..efee4ac3 100644 --- a/examples/maven-spring-compile/src/test/java/community/flock/wirespec/example/gradle/app/user/UserTest.java +++ b/examples/maven-spring-compile/src/test/java/community/flock/wirespec/example/maven/custom/app/user/UserTest.java @@ -1,13 +1,13 @@ -package community.flock.wirespec.example.gradle.app.user; +package community.flock.wirespec.example.maven.custom.app.user; import org.junit.jupiter.api.Test; import java.util.function.Consumer; -import static community.flock.wirespec.example.gradle.app.user.UserContext.Service.deleteUserByName; -import static community.flock.wirespec.example.gradle.app.user.UserContext.Service.getAllUsers; -import static community.flock.wirespec.example.gradle.app.user.UserContext.Service.getUserByName; -import static community.flock.wirespec.example.gradle.app.user.UserContext.Service.saveUser; +import static community.flock.wirespec.example.maven.custom.app.user.UserContext.Service.deleteUserByName; +import static community.flock.wirespec.example.maven.custom.app.user.UserContext.Service.getAllUsers; +import static community.flock.wirespec.example.maven.custom.app.user.UserContext.Service.getUserByName; +import static community.flock.wirespec.example.maven.custom.app.user.UserContext.Service.saveUser; import static org.junit.jupiter.api.Assertions.assertEquals; class UserTest { diff --git a/examples/maven-spring-convert/src/main/kotlin/community/flock/wirespec/examples/open_api_app/KotlinPetstoreClient.kt b/examples/maven-spring-convert/src/main/kotlin/community/flock/wirespec/examples/openapi/app/KotlinPetstoreClient.kt similarity index 97% rename from examples/maven-spring-convert/src/main/kotlin/community/flock/wirespec/examples/open_api_app/KotlinPetstoreClient.kt rename to examples/maven-spring-convert/src/main/kotlin/community/flock/wirespec/examples/openapi/app/KotlinPetstoreClient.kt index 7acb6dac..87c7c8b1 100644 --- a/examples/maven-spring-convert/src/main/kotlin/community/flock/wirespec/examples/open_api_app/KotlinPetstoreClient.kt +++ b/examples/maven-spring-convert/src/main/kotlin/community/flock/wirespec/examples/openapi/app/KotlinPetstoreClient.kt @@ -1,4 +1,4 @@ -package community.flock.wirespec.examples.open_api_app +package community.flock.wirespec.examples.openapi.app import community.flock.wirespec.Wirespec import community.flock.wirespec.generated.kotlin.v3.AddPetEndpoint diff --git a/examples/maven-spring-convert/src/main/kotlin/community/flock/wirespec/examples/open_api_app/KotlinPetstoreController.kt b/examples/maven-spring-convert/src/main/kotlin/community/flock/wirespec/examples/openapi/app/KotlinPetstoreController.kt similarity index 96% rename from examples/maven-spring-convert/src/main/kotlin/community/flock/wirespec/examples/open_api_app/KotlinPetstoreController.kt rename to examples/maven-spring-convert/src/main/kotlin/community/flock/wirespec/examples/openapi/app/KotlinPetstoreController.kt index 5ef77dfc..77d4dc39 100644 --- a/examples/maven-spring-convert/src/main/kotlin/community/flock/wirespec/examples/open_api_app/KotlinPetstoreController.kt +++ b/examples/maven-spring-convert/src/main/kotlin/community/flock/wirespec/examples/openapi/app/KotlinPetstoreController.kt @@ -1,4 +1,4 @@ -package community.flock.wirespec.examples.open_api_app +package community.flock.wirespec.examples.openapi.app import community.flock.wirespec.generated.kotlin.v3.AddPetEndpoint import community.flock.wirespec.generated.kotlin.v3.FindPetsByStatusEndpoint @@ -35,5 +35,4 @@ class KotlinPetstoreController( else -> error("No response") } } - } diff --git a/examples/maven-spring-convert/src/main/kotlin/community/flock/wirespec/examples/open_api_app/OpenApiApplication.kt b/examples/maven-spring-convert/src/main/kotlin/community/flock/wirespec/examples/openapi/app/OpenApiApplication.kt similarity index 81% rename from examples/maven-spring-convert/src/main/kotlin/community/flock/wirespec/examples/open_api_app/OpenApiApplication.kt rename to examples/maven-spring-convert/src/main/kotlin/community/flock/wirespec/examples/openapi/app/OpenApiApplication.kt index 0a11b332..e3658303 100644 --- a/examples/maven-spring-convert/src/main/kotlin/community/flock/wirespec/examples/open_api_app/OpenApiApplication.kt +++ b/examples/maven-spring-convert/src/main/kotlin/community/flock/wirespec/examples/openapi/app/OpenApiApplication.kt @@ -1,4 +1,4 @@ -package community.flock.wirespec.examples.open_api_app +package community.flock.wirespec.examples.openapi.app import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.runApplication diff --git a/examples/maven-spring-convert/src/main/kotlin/community/flock/wirespec/examples/open_api_app/OpenApiConfiguration.kt b/examples/maven-spring-convert/src/main/kotlin/community/flock/wirespec/examples/openapi/app/OpenApiConfiguration.kt similarity index 62% rename from examples/maven-spring-convert/src/main/kotlin/community/flock/wirespec/examples/open_api_app/OpenApiConfiguration.kt rename to examples/maven-spring-convert/src/main/kotlin/community/flock/wirespec/examples/openapi/app/OpenApiConfiguration.kt index b2452ad5..9120a8bd 100644 --- a/examples/maven-spring-convert/src/main/kotlin/community/flock/wirespec/examples/open_api_app/OpenApiConfiguration.kt +++ b/examples/maven-spring-convert/src/main/kotlin/community/flock/wirespec/examples/openapi/app/OpenApiConfiguration.kt @@ -1,4 +1,4 @@ -package community.flock.wirespec.examples.open_api_app +package community.flock.wirespec.examples.openapi.app import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration @@ -8,7 +8,5 @@ import org.springframework.web.client.RestTemplate class OpenApiConfiguration { @Bean - fun restTemplate(): RestTemplate { - return RestTemplate() - } -} \ No newline at end of file + fun restTemplate() = RestTemplate() +} diff --git a/examples/maven-spring-convert/src/main/kotlin/community/flock/wirespec/examples/open_api_app/WirespecConfiguration.kt b/examples/maven-spring-convert/src/main/kotlin/community/flock/wirespec/examples/openapi/app/WirespecConfiguration.kt similarity index 95% rename from examples/maven-spring-convert/src/main/kotlin/community/flock/wirespec/examples/open_api_app/WirespecConfiguration.kt rename to examples/maven-spring-convert/src/main/kotlin/community/flock/wirespec/examples/openapi/app/WirespecConfiguration.kt index 1652301c..6541aba2 100644 --- a/examples/maven-spring-convert/src/main/kotlin/community/flock/wirespec/examples/open_api_app/WirespecConfiguration.kt +++ b/examples/maven-spring-convert/src/main/kotlin/community/flock/wirespec/examples/openapi/app/WirespecConfiguration.kt @@ -1,6 +1,6 @@ @file:OptIn(ExperimentalStdlibApi::class) -package community.flock.wirespec.examples.open_api_app +package community.flock.wirespec.examples.openapi.app import com.fasterxml.jackson.databind.ObjectMapper import community.flock.wirespec.Wirespec diff --git a/examples/maven-spring-custom/README.md b/examples/maven-spring-custom/README.md index 91761f02..d6816afe 100644 --- a/examples/maven-spring-custom/README.md +++ b/examples/maven-spring-custom/README.md @@ -14,7 +14,7 @@ ${project.basedir}/src/main/wirespec ${project.build.directory}/generated-sources/java/hello - community.flock.wirespec.emit.CustomEmitter + community.flock.wirespec.example.maven.custom.emit.CustomEmitter java true diff --git a/examples/maven-spring-custom/app/pom.xml b/examples/maven-spring-custom/app/pom.xml index 7a83fbb7..fb42a082 100644 --- a/examples/maven-spring-custom/app/pom.xml +++ b/examples/maven-spring-custom/app/pom.xml @@ -49,7 +49,7 @@ ${project.basedir}/src/main/wirespec ${project.build.directory}/generated-sources/java/hello - community.flock.wirespec.emit.CustomEmitter + community.flock.wirespec.example.maven.custom.emit.CustomEmitter java true diff --git a/examples/maven-spring-custom/app/src/main/java/community/flock/wirespec/example/gradle/app/TodoApplication.java b/examples/maven-spring-custom/app/src/main/java/community/flock/wirespec/example/maven/custom/app/TodoApplication.java similarity index 83% rename from examples/maven-spring-custom/app/src/main/java/community/flock/wirespec/example/gradle/app/TodoApplication.java rename to examples/maven-spring-custom/app/src/main/java/community/flock/wirespec/example/maven/custom/app/TodoApplication.java index 48944753..da91c321 100644 --- a/examples/maven-spring-custom/app/src/main/java/community/flock/wirespec/example/gradle/app/TodoApplication.java +++ b/examples/maven-spring-custom/app/src/main/java/community/flock/wirespec/example/maven/custom/app/TodoApplication.java @@ -1,4 +1,4 @@ -package community.flock.wirespec.example.gradle.app; +package community.flock.wirespec.example.maven.custom.app; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; diff --git a/examples/maven-spring-custom/app/src/main/java/community/flock/wirespec/example/gradle/app/TodoController.java b/examples/maven-spring-custom/app/src/main/java/community/flock/wirespec/example/maven/custom/app/TodoController.java similarity index 90% rename from examples/maven-spring-custom/app/src/main/java/community/flock/wirespec/example/gradle/app/TodoController.java rename to examples/maven-spring-custom/app/src/main/java/community/flock/wirespec/example/maven/custom/app/TodoController.java index b7da9902..ab60c994 100644 --- a/examples/maven-spring-custom/app/src/main/java/community/flock/wirespec/example/gradle/app/TodoController.java +++ b/examples/maven-spring-custom/app/src/main/java/community/flock/wirespec/example/maven/custom/app/TodoController.java @@ -1,4 +1,4 @@ -package community.flock.wirespec.example.gradle.app; +package community.flock.wirespec.example.maven.custom.app; import hello.TodoCustom; import hello.TodoIdCustom; diff --git a/examples/maven-spring-custom/emitter/src/main/java/community/flock/wirespec/emit/CustomEmitter.java b/examples/maven-spring-custom/emitter/src/main/java/community/flock/wirespec/example/maven/custom/emit/CustomEmitter.java similarity index 98% rename from examples/maven-spring-custom/emitter/src/main/java/community/flock/wirespec/emit/CustomEmitter.java rename to examples/maven-spring-custom/emitter/src/main/java/community/flock/wirespec/example/maven/custom/emit/CustomEmitter.java index 4bf3c058..b956b195 100644 --- a/examples/maven-spring-custom/emitter/src/main/java/community/flock/wirespec/emit/CustomEmitter.java +++ b/examples/maven-spring-custom/emitter/src/main/java/community/flock/wirespec/example/maven/custom/emit/CustomEmitter.java @@ -1,4 +1,4 @@ -package community.flock.wirespec.emit; +package community.flock.wirespec.example.maven.custom.emit; import community.flock.wirespec.compiler.core.emit.common.DefinitionModelEmitter; import community.flock.wirespec.compiler.core.emit.common.Emitted; diff --git a/examples/maven-spring-integration/.gitignore b/examples/maven-spring-integration/.gitignore new file mode 100644 index 00000000..bc981dd3 --- /dev/null +++ b/examples/maven-spring-integration/.gitignore @@ -0,0 +1,3 @@ +.idea +target +src/main/typescript/generated diff --git a/examples/maven-spring-integration/README.md b/examples/maven-spring-integration/README.md new file mode 100644 index 00000000..fa754c8a --- /dev/null +++ b/examples/maven-spring-integration/README.md @@ -0,0 +1,75 @@ +# Example: How to use the spring integration lib + +## Wirespec Maven Plugin Configuration +```xml + + community.flock.wirespec.plugin.maven + wirespec-maven-plugin + 0.0.0-SNAPSHOT + + + community.flock.wirespec.integration + spring-jvm + 0.0.0-SNAPSHOT + + + + + java + + custom + + + ${project.basedir}/src/main/wirespec + ${project.build.directory}/generated-sources + community.flock.wirespec.integration.spring.java.emit.SpringJavaEmitter + Java + java + community.flock.wirespec.generated.examples.spring + + + + +``` + +Find the [actual pom.xml](app/pom.xml) in the `app` module and the + +## Usage + +The custom emitter 'SpringJavaEmitter' will generate interfaces where the handle function is annotated with spring boot annotations. + +```java +public interface GetTodoByIdEndpoint extends Wirespec.Endpoint { + ... + interface Handler extends Wirespec.Handler { + ... + @org.springframework.web.bind.annotation.GetMapping("/todos/{id}") + java.util.concurrent.CompletableFuture> getTodoById(Request request); + } +} +``` + +These interface can be implemented as RestControllers. Spring will load the controllers and makes them available as request handlers + +```java +@RestController +class TodoController implements GetTodosEndpoint.Handler{ + @Override + public CompletableFuture> getTodoById(GetTodoByIdEndpoint.Request request) { + ... + } +} +``` + +By loading the 'WirespecConfiguration' on the application Wirespec request and response objects will be handled correctly. + +```java +@SpringBootApplication +@Import(WirespecConfiguration.class) +public class TodoApplication { + public static void main(String[] args) { + SpringApplication.run(TodoApplication.class, args); + } +} + +``` \ No newline at end of file diff --git a/examples/maven-spring-integration/pom.xml b/examples/maven-spring-integration/pom.xml new file mode 100644 index 00000000..b243d4d7 --- /dev/null +++ b/examples/maven-spring-integration/pom.xml @@ -0,0 +1,76 @@ + + + 4.0.0 + + + community.flock.wirespec.example.maven + examples + 0.0.0-SNAPSHOT + + + maven-spring-integration + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-test + test + + + community.flock.wirespec.integration + spring-jvm + 0.0.0-SNAPSHOT + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + community.flock.wirespec.plugin.maven + wirespec-maven-plugin + 0.0.0-SNAPSHOT + + + community.flock.wirespec.integration + spring-jvm + 0.0.0-SNAPSHOT + + + + + java + + custom + + + ${project.basedir}/src/main/wirespec + ${project.build.directory}/generated-sources + community.flock.wirespec.integration.spring.java.emit.SpringJavaEmitter + Java + java + community.flock.wirespec.generated.examples.spring + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.0 + + 21 + --enable-preview + + + + + diff --git a/examples/maven-spring-integration/src/main/java/community/flock/wirespec/examples/maven/spring/integration/TodoApplication.java b/examples/maven-spring-integration/src/main/java/community/flock/wirespec/examples/maven/spring/integration/TodoApplication.java new file mode 100644 index 00000000..2db2169e --- /dev/null +++ b/examples/maven-spring-integration/src/main/java/community/flock/wirespec/examples/maven/spring/integration/TodoApplication.java @@ -0,0 +1,14 @@ +package community.flock.wirespec.examples.maven.spring.integration; + +import community.flock.wirespec.integration.spring.java.configuration.WirespecConfiguration; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Import; + +@SpringBootApplication +@Import(WirespecConfiguration.class) +public class TodoApplication { + public static void main(String[] args) { + SpringApplication.run(TodoApplication.class, args); + } +} diff --git a/examples/maven-spring-integration/src/main/java/community/flock/wirespec/examples/maven/spring/integration/TodoController.java b/examples/maven-spring-integration/src/main/java/community/flock/wirespec/examples/maven/spring/integration/TodoController.java new file mode 100644 index 00000000..70c9fc1d --- /dev/null +++ b/examples/maven-spring-integration/src/main/java/community/flock/wirespec/examples/maven/spring/integration/TodoController.java @@ -0,0 +1,64 @@ +package community.flock.wirespec.examples.maven.spring.integration; + +import community.flock.wirespec.generated.examples.spring.CreateTodoEndpoint; +import community.flock.wirespec.generated.examples.spring.DeleteTodoEndpoint; +import community.flock.wirespec.generated.examples.spring.GetTodoByIdEndpoint; +import community.flock.wirespec.generated.examples.spring.GetTodosEndpoint; +import community.flock.wirespec.generated.examples.spring.Todo; +import community.flock.wirespec.generated.examples.spring.UpdateTodoEndpoint; +import org.springframework.web.bind.annotation.RestController; + +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +import static java.lang.Integer.parseInt; + +@RestController +class TodoController implements GetTodosEndpoint.Handler, GetTodoByIdEndpoint.Handler, CreateTodoEndpoint.Handler, UpdateTodoEndpoint.Handler, DeleteTodoEndpoint.Handler { + + private final TodoService service; + + public TodoController(TodoService service) { + this.service = service; + } + + @Override + public CompletableFuture> createTodo(CreateTodoEndpoint.Request request) { + var todoInput = switch (request){ + case CreateTodoEndpoint.Request req -> req.getBody(); + }; + var todo = new Todo( + UUID.randomUUID().toString(), + todoInput.name(), + todoInput.done() + ); + service.create(todo); + var res = new CreateTodoEndpoint.Response200(todo); + return CompletableFuture.completedFuture(res); + } + + @Override + public CompletableFuture> deleteTodo(DeleteTodoEndpoint.Request request) { + return null; + } + + @Override + public CompletableFuture> getTodoById(GetTodoByIdEndpoint.Request request) { + var id = switch (request){ + case GetTodoByIdEndpoint.Request req -> req.getPath().id(); + }; + var res = new GetTodoByIdEndpoint.Response200(service.store.get(parseInt(id))); + return CompletableFuture.completedFuture(res); + } + + @Override + public CompletableFuture> updateTodo(UpdateTodoEndpoint.Request request) { + return null; + } + + @Override + public CompletableFuture> getTodos(GetTodosEndpoint.Request request) { + var res = new GetTodosEndpoint.Response200(service.store); + return CompletableFuture.completedFuture(res); + } +} diff --git a/examples/maven-spring-integration/src/main/java/community/flock/wirespec/examples/maven/spring/integration/TodoService.java b/examples/maven-spring-integration/src/main/java/community/flock/wirespec/examples/maven/spring/integration/TodoService.java new file mode 100644 index 00000000..ffaf7425 --- /dev/null +++ b/examples/maven-spring-integration/src/main/java/community/flock/wirespec/examples/maven/spring/integration/TodoService.java @@ -0,0 +1,52 @@ +package community.flock.wirespec.examples.maven.spring.integration; + +import community.flock.wirespec.generated.examples.spring.Todo; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +import static java.util.stream.Collectors.toList; + +@Service +public class TodoService { + + public List store = new ArrayList<>(); + + public TodoService(){ + store.add(new Todo(UUID.randomUUID().toString(), "First todo", false)); + store.add(new Todo(UUID.randomUUID().toString(), "Second todo", false)); + store.add(new Todo(UUID.randomUUID().toString(), "Third todo", false)); + } + + public CompletableFuture create(Todo todo) { + store.add(todo); + return CompletableFuture.completedFuture(todo); + } + + public CompletableFuture update(String id, Todo todo) { + store = store.stream() + .map(it -> { + if (it.id().equals(id)) { + return todo; + } else { + return it; + } + }) + .collect(toList()); + return CompletableFuture.completedFuture(todo); + } + + public CompletableFuture delete(String id) { + return store.stream() + .filter(todo -> todo.id().equals(id)) + .findFirst() + .map(todo -> { + store.remove(todo); + return CompletableFuture.completedFuture(todo); + }) + .orElseThrow(); + } +} diff --git a/examples/maven-spring-integration/src/main/wirespec/todo.ws b/examples/maven-spring-integration/src/main/wirespec/todo.ws new file mode 100644 index 00000000..7cf2dc18 --- /dev/null +++ b/examples/maven-spring-integration/src/main/wirespec/todo.ws @@ -0,0 +1,40 @@ +endpoint GetTodos GET /todos ? {done: Boolean?} -> { + 200 -> Todo[] + 404 -> Error +} + +endpoint CreateTodo POST TodoInput /todos -> { + 200 -> Todo + 404 -> Error +} + +endpoint GetTodoById GET /todos/{id: String} -> { + 200 -> Todo + 404 -> Error +} + +endpoint UpdateTodo PUT TodoInput /todos/{id: String} -> { + 200 -> Todo + 404 -> Error +} + +endpoint DeleteTodo DELETE /todos/{id: String} -> { + 200 -> Todo + 404 -> Error +} + +type Todo { + id: String, + name: String, + done: Boolean +} + +type TodoInput { + name: String, + done: Boolean +} + +type Error { + code: String, + description: String? +} diff --git a/examples/maven-spring-integration/src/test/java/community/flock/wirespec/examples/maven/spring/integration/TodoTest.java b/examples/maven-spring-integration/src/test/java/community/flock/wirespec/examples/maven/spring/integration/TodoTest.java new file mode 100644 index 00000000..d2e22fb1 --- /dev/null +++ b/examples/maven-spring-integration/src/test/java/community/flock/wirespec/examples/maven/spring/integration/TodoTest.java @@ -0,0 +1,51 @@ +package community.flock.wirespec.examples.maven.spring.integration; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasSize; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.asyncDispatch; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.request; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest() +@AutoConfigureMockMvc +public class TodoTest { + + @Autowired + private MockMvc mockMvc; + + @Test + void shouldReturnDefaultMessage() throws Exception { + MvcResult mvcGetResult = mockMvc + .perform(get("/todos")) + .andExpect(request().asyncStarted()) + .andReturn(); + + this.mockMvc.perform(asyncDispatch(mvcGetResult)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", hasSize(3))); + + MvcResult mvcPostResult = mockMvc + .perform(post("/todos") + .contentType(APPLICATION_JSON_VALUE) + .content("{\"name\":\"test\", \"done\": true}")) + .andExpect(request().asyncStarted()) + .andReturn(); + + this.mockMvc.perform(asyncDispatch(mvcPostResult)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.name", equalTo("test"))) + .andExpect(jsonPath("$.done", equalTo(true))); + + } +} diff --git a/examples/pom.xml b/examples/pom.xml index 3f48a327..38a1cff8 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -16,6 +16,7 @@ maven-spring-compile maven-spring-convert maven-spring-custom + maven-spring-integration diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7c7903f6..3ac6a885 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -9,10 +9,12 @@ intellij = "2.0.0" jackson_databind = "2.16.1" jackson_kotlin = "2.14.2" java = "17" +junit_launcher = "1.11.3" kotest = "5.9.1" kotest_arrow = "1.4.0" kotlin = "2.0.0" kotlin_compiler = "2.0" +kotlinx_coroutines = "1.9.0" kotlinx_openapi_bindings = "0.0.24" kotlinx_resources = "0.9.0" kotlinx_rgxgen = "0.0.1" @@ -21,17 +23,26 @@ maven_plugin = "0.4.3" maven_plugin_api = "3.9.8" maven_plugin_annotations = "3.13.1" maven_project = "2.2.1" +spring_boot = "3.3.4" +spring_dependency_management = "1.1.6" [libraries] arrow_core = { module = "io.arrow-kt:arrow-core", version.ref = "arrow" } clikt = { module = "com.github.ajalt.clikt:clikt", version.ref = "clikt" } jackson_databind = { module = "com.fasterxml.jackson.core:jackson-databind", version.ref = "jackson_databind" } jackson_kotlin = { module = "com.fasterxml.jackson.module:jackson-module-kotlin", version.ref = "jackson_kotlin" } +junit_launcher = { module = "org.junit.platform:junit-platform-launcher", version.ref = "junit_launcher" } kotest_assertions = { module = "io.kotest:kotest-assertions-core", version.ref = "kotest" } kotest_assertions_arrow = { module = "io.kotest.extensions:kotest-assertions-arrow", version.ref = "kotest_arrow" } kotest_engine = { module = "io.kotest:kotest-framework-engine", version.ref = "kotest" } +kotlin_junit = { module = "org.jetbrains.kotlin:kotlin-test-junit5", version.ref = "kotlin" } kotlin_reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin" } kotlin_stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" } +kotlin_test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } +kotlin_test_common = { module = "org.jetbrains.kotlin:kotlin-test-common", version.ref = "kotlin" } +kotlin_test_annotations_common = { module = "org.jetbrains.kotlin:kotlin-test-annotations-common", version.ref = "kotlin" } +kotlin_test_junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" } +kotlinx_coroutines_reactor = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-reactor", version.ref = "kotlinx_coroutines" } kotlinx_openapi_bindings = { module = "community.flock.kotlinx.openapi.bindings:kotlin-openapi-bindings", version.ref = "kotlinx_openapi_bindings" } kotlinx_resources = { module = "com.goncalossilva:resources", version.ref = "kotlinx_resources" } kotlinx_rgxgen = { module = "community.flock.kotlinx.rgxgen:kotlin-rgxgen", version.ref = "kotlinx_rgxgen" } @@ -39,9 +50,12 @@ kotlinx_serialization = { module = "org.jetbrains.kotlinx:kotlinx-serialization- maven_plugin_api = { module = "org.apache.maven:maven-plugin-api", version.ref = "maven_plugin_api" } maven_plugin_annotations = { module = "org.apache.maven.plugin-tools:maven-plugin-annotations", version.ref = "maven_plugin_annotations" } maven_project = { module = "org.apache.maven:maven-project", version.ref = "maven_project" } +spring_boot_web = { module = "org.springframework.boot:spring-boot-starter-web", version.ref = "spring_boot" } +spring_boot_test = { module = "org.springframework.boot:spring-boot-starter-test", version.ref = "spring_boot" } [bundles] jackson = ["jackson_databind", "jackson_kotlin"] +kotlin_test = ["kotlin_test_common", "kotlin_test_annotations_common", "kotlin_test_junit"] kotest = ["kotest_engine", "kotest_assertions", "kotest_assertions_arrow"] maven_plugin = ["maven_plugin_api", "maven_plugin_annotations", "maven_project"] @@ -53,3 +67,5 @@ kotlin_jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } kotlin_multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } kotlinx_resources = { id = "com.goncalossilva.resources", version.ref = "kotlinx_resources" } maven_plugin = { id = "de.benediktritter.maven-plugin-development", version.ref = "maven_plugin" } +spring_boot = { id = "org.springframework.boot", version.ref = "spring_boot" } +spring_dependency_management = { id = "io.spring.dependency-management", version.ref = "spring_dependency_management" } diff --git a/scripts/example.sh b/scripts/example.sh index d1d132fc..852cad42 100755 --- a/scripts/example.sh +++ b/scripts/example.sh @@ -3,6 +3,9 @@ dir="$(dirname -- "$0")" ./gradlew jvmTest && + ./gradlew src:integration:wirespec:publishToMavenLocal && + ./gradlew src:integration:jackson:publishToMavenLocal && + ./gradlew src:integration:spring:publishToMavenLocal && ./gradlew src:plugin:gradle:publishToMavenLocal && ./gradlew src:plugin:maven:publishToMavenLocal && (cd "$dir"/../src/ide/vscode && npm i && npm run build) && diff --git a/settings.gradle.kts b/settings.gradle.kts index 9db5a20c..acd2c8f8 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -27,5 +27,6 @@ include( "src:converter:openapi", "src:integration:jackson", "src:integration:wirespec", + "src:integration:spring", "src:tools:generator", ) diff --git a/src/compiler/core/build.gradle.kts b/src/compiler/core/build.gradle.kts index 0154ea19..9d4ddeaa 100644 --- a/src/compiler/core/build.gradle.kts +++ b/src/compiler/core/build.gradle.kts @@ -43,7 +43,7 @@ kotlin { } commonTest { dependencies { - implementation(kotlin("test")) + implementation(libs.kotlin.test) implementation(libs.bundles.kotest) } } diff --git a/src/compiler/core/src/commonMain/kotlin/community/flock/wirespec/compiler/core/emit/JavaEmitter.kt b/src/compiler/core/src/commonMain/kotlin/community/flock/wirespec/compiler/core/emit/JavaEmitter.kt index 8523f38c..271ca4db 100644 --- a/src/compiler/core/src/commonMain/kotlin/community/flock/wirespec/compiler/core/emit/JavaEmitter.kt +++ b/src/compiler/core/src/commonMain/kotlin/community/flock/wirespec/compiler/core/emit/JavaEmitter.kt @@ -34,12 +34,12 @@ open class JavaEmitter( """.trimMargin() override fun Definition.emitName(): String = when (this) { - is Endpoint -> "${identifier.emit()}Endpoint" - is Channel -> "${identifier.emit()}Channel" - is Enum -> identifier.emit() - is Refined -> identifier.emit() - is Type -> identifier.emit() - is Union -> identifier.emit() + is Endpoint -> "${identifier.emitClassName()}Endpoint" + is Channel -> "${identifier.emitClassName()}Channel" + is Enum -> identifier.emitClassName() + is Refined -> identifier.emitClassName() + is Type -> identifier.emitClassName() + is Union -> identifier.emitClassName() } override fun notYetImplemented() = @@ -78,7 +78,7 @@ open class JavaEmitter( override fun Type.Shape.emit() = value.joinToString("\n") { "${Spacer}${it.emit()}," }.dropLast(1) override fun Field.emit() = - "${if (isNullable) "java.util.Optional<${reference.emit()}>" else reference.emit()} ${identifier.emit().firstToLower()}" + "${if (isNullable) "java.util.Optional<${reference.emit()}>" else reference.emit()} ${identifier.emitVariableName()}" override fun Reference.emit() = emitType() .let { if (isIterable) "java.util.List<$it>" else it } @@ -105,10 +105,12 @@ open class JavaEmitter( Reference.Primitive.Type.Boolean -> "Boolean" } - override fun Identifier.emit() = value.sanitizeKeywords().sanitizeSymbol() + override fun Identifier.emitClassName() = if (value in reservedKeywords) "_$value" else value + override fun Identifier.emitVariableName() = value.firstToLower() + .let { if (it in reservedKeywords) "_$it" else it } override fun emit(refined: Refined) = """ - |public record ${refined.identifier.emit()} (String value) implements Wirespec.Refined { + |public record ${refined.identifier.emitClassName()} (String value) implements Wirespec.Refined { |${Spacer}@Override |${Spacer}public String toString() { return value; } |${Spacer}public static boolean validate(${refined.emitName()} record) { @@ -123,10 +125,10 @@ open class JavaEmitter( """${Spacer}return java.util.regex.Pattern.compile("${expression.replace("\\", "\\\\")}").matcher(record.value).find();""" override fun emit(enum: Enum) = """ - |public enum ${enum.identifier.emit()} implements Wirespec.Enum { + |public enum ${enum.identifier.emitClassName()} implements Wirespec.Enum { |${enum.entries.joinToString(",\n") { "${it.sanitizeEnum().sanitizeKeywords()}(\"$it\")" }.spacer()}; |${Spacer}public final String label; - |${Spacer}${enum.identifier.emit()}(String label) { + |${Spacer}${enum.identifier.emitClassName()}(String label) { |${Spacer(2)}this.label = label; |${Spacer}} |${Spacer}@Override @@ -141,13 +143,13 @@ open class JavaEmitter( """.trimMargin() override fun emit(channel: Channel) = """ - |interface ${channel.identifier.emit()}Channel { + |interface ${channel.identifier.emitClassName()}Channel { | void invoke(${channel.reference.emitWrap(channel.isNullable)} message) |} """.trimMargin() override fun emit(endpoint: Endpoint) = """ - |public interface ${endpoint.identifier.emit()}Endpoint extends Wirespec.Endpoint { + |public interface ${endpoint.identifier.emitClassName()}Endpoint extends Wirespec.Endpoint { |${endpoint.pathParams.emitObject("Path", "Wirespec.Path") { it.emit() }} | |${endpoint.queries.emitObject("Queries", "Wirespec.Queries") { it.emit() }} @@ -177,7 +179,7 @@ open class JavaEmitter( |${Spacer(3)}}; |${Spacer(2)}} | - |${Spacer(2)}java.util.concurrent.CompletableFuture> ${endpoint.identifier.emit().firstToLower()}(Request request); + |${Spacer(2)}${emitHandleFunction(endpoint)} |${Spacer(2)}class Handlers implements Wirespec.Server>, Wirespec.Client> { |${Spacer(3)}@Override public String getPathTemplate() { return "/${endpoint.path.joinToString("/") { it.emit() }}"; } |${Spacer(3)}@Override public String getMethod() { return "${endpoint.method}"; } @@ -198,6 +200,9 @@ open class JavaEmitter( |} """.trimMargin() + open fun emitHandleFunction(endpoint: Endpoint) = + "java.util.concurrent.CompletableFuture> ${endpoint.identifier.emitVariableName()}(Request request);" + private fun Endpoint.emitResponseInterfaces() = responses .distinctBy { it.status.first() } .joinToString("\n") { "${Spacer}sealed interface Response${it.status[0]}XX extends Response {}" } @@ -250,7 +255,7 @@ open class JavaEmitter( ).joinToString() }) implements Response${status.first()}XX<${content.emit()}> { |${Spacer(2)}@Override public int getStatus() { return ${status.fixStatus()}; } - |${Spacer(2)}${headers.joinToString { it.identifier.emit().firstToLower() }.let { "@Override public Headers getHeaders() { return new Headers($it); }" }} + |${Spacer(2)}${headers.joinToString { it.identifier.emitVariableName() }.let { "@Override public Headers getHeaders() { return new Headers($it); }" }} |${Spacer(2)}@Override public ${content.emit()} getBody() { return ${if(content == null) "null" else "body"}; } |${Spacer(1)}${headers.emitObject("Headers", "Wirespec.Response.Headers") { it.emit() }} |${Spacer}} @@ -266,10 +271,10 @@ open class JavaEmitter( ).joinToString() }) {\n${Spacer(3)}${ listOfNotNull( - endpoint.pathParams.joinToString { it.identifier.emit().firstToLower() }.let { "this.path = new Path($it);" }, + endpoint.pathParams.joinToString { it.identifier.emitVariableName() }.let { "this.path = new Path($it);" }, "this.method = Wirespec.Method.${endpoint.method.name};", - endpoint.queries.joinToString { it.identifier.emit().firstToLower() }.let { "this.queries = new Queries($it);" }, - endpoint.headers.joinToString { it.identifier.emit().firstToLower() } + endpoint.queries.joinToString { it.identifier.emitVariableName() }.let { "this.queries = new Queries($it);" }, + endpoint.headers.joinToString { it.identifier.emitVariableName() } .let { "this.headers = new RequestHeaders($it);" }, "this.body = ${content?.let { "body" } ?: "null"};" ).joinToString("\n${Spacer(3)}") @@ -294,7 +299,7 @@ open class JavaEmitter( """${Spacer(4)}case $status -> new Response${status.firstToUpper()}(${this.emitDeserializedParams()});""" private fun Field.emitSerialized(fields: String) = - """java.util.Map.entry("${identifier.value}", serialization.serialize(request.$fields.${identifier.emit().firstToLower()}, Wirespec.getType(${reference.emitType()}.class, ${reference.isIterable})))""" + """java.util.Map.entry("${identifier.value}", serialization.serialize(request.$fields.${identifier.emitVariableName()}, Wirespec.getType(${reference.emitType()}.class, ${reference.isIterable})))""" private fun IndexedValue.emitDeserialized() = """${Spacer(4)}serialization.<${value.reference.emit()}>deserialize(request.path().get(${index}), Wirespec.getType(${value.reference.emitType()}.class, ${value.reference.isIterable}))""" @@ -302,12 +307,12 @@ open class JavaEmitter( private fun Field.emitDeserialized(fields: String) = """${Spacer(4)}java.util.Optional.ofNullable(request.$fields().get("${identifier.value}")).map(it -> serialization.<${reference.emit()}>deserialize(it, Wirespec.getType(${reference.emitType()}.class, ${reference.isIterable})))${if(!isNullable) ".get()" else ""}""" - private fun Endpoint.Segment.Param.emitIdentifier() = "serialization.serialize(request.path.${identifier.emit().firstToLower()}, Wirespec.getType(${reference.emitType()}.class, ${reference.isIterable}))" + private fun Endpoint.Segment.Param.emitIdentifier() = "serialization.serialize(request.path.${identifier.emitVariableName().firstToLower()}, Wirespec.getType(${reference.emitType()}.class, ${reference.isIterable}))" private fun Endpoint.Content?.emitType() = this?.reference?.emitType() ?: "Void" private fun Endpoint.Content?.emit() = this?.reference?.emit() ?: "Void" - private fun Endpoint.Segment.Param.emit() = "${reference.emit()} ${identifier.emit().firstToLower()}" + private fun Endpoint.Segment.Param.emit() = "${reference.emit()} ${identifier.emitVariableName()}" private fun Reference.emitWrap(isNullable: Boolean): String = value .let { if (isIterable) "java.util.Optional<$it>" else it } diff --git a/src/compiler/core/src/commonMain/kotlin/community/flock/wirespec/compiler/core/emit/JavaLegacyEmitter.kt b/src/compiler/core/src/commonMain/kotlin/community/flock/wirespec/compiler/core/emit/JavaLegacyEmitter.kt index b44e8099..5b3922cf 100644 --- a/src/compiler/core/src/commonMain/kotlin/community/flock/wirespec/compiler/core/emit/JavaLegacyEmitter.kt +++ b/src/compiler/core/src/commonMain/kotlin/community/flock/wirespec/compiler/core/emit/JavaLegacyEmitter.kt @@ -63,12 +63,12 @@ open class JavaLegacyEmitter( } override fun Definition.emitName(): String = when (this) { - is Endpoint -> "${identifier.emit()}Endpoint" - is Channel -> "${identifier.emit()}Channel" - is Enum -> identifier.emit() - is Refined -> identifier.emit() - is Type -> identifier.emit() - is Union -> identifier.emit() + is Endpoint -> "${identifier.emitClassName()}Endpoint" + is Channel -> "${identifier.emitClassName()}Channel" + is Enum -> identifier.emitClassName() + is Refined -> identifier.emitClassName() + is Type -> identifier.emitClassName() + is Union -> identifier.emitClassName() } override fun notYetImplemented() = @@ -129,7 +129,7 @@ open class JavaLegacyEmitter( override fun emit(channel: Channel): String = """ - |interface ${channel.identifier.emit()}Channel { + |interface ${channel.identifier.emitClassName()}Channel { | void invoke(${channel.reference.transform(channel.isNullable, false).emitWrap()} message) |} """.trimMargin() diff --git a/src/compiler/core/src/commonMain/kotlin/community/flock/wirespec/compiler/core/emit/KotlinEmitter.kt b/src/compiler/core/src/commonMain/kotlin/community/flock/wirespec/compiler/core/emit/KotlinEmitter.kt index 511dbf35..f235f717 100644 --- a/src/compiler/core/src/commonMain/kotlin/community/flock/wirespec/compiler/core/emit/KotlinEmitter.kt +++ b/src/compiler/core/src/commonMain/kotlin/community/flock/wirespec/compiler/core/emit/KotlinEmitter.kt @@ -36,12 +36,12 @@ open class KotlinEmitter( """.trimMargin() override fun Definition.emitName(): String = when (this) { - is Endpoint -> "${identifier.emit()}Endpoint" - is Channel -> "${identifier.emit()}Channel" - is Enum -> identifier.emit() - is Refined -> identifier.emit() - is Type -> identifier.emit() - is Union -> identifier.emit() + is Endpoint -> "${identifier.emitClassName()}Endpoint" + is Channel -> "${identifier.emitClassName()}Channel" + is Enum -> identifier.emitClassName() + is Refined -> identifier.emitClassName() + is Type -> identifier.emitClassName() + is Union -> identifier.emitClassName() } override fun notYetImplemented() = @@ -62,15 +62,17 @@ open class KotlinEmitter( ) } - override fun emit(type: Type, ast: AST) = """ - |data class ${type.emitName()}( - |${type.shape.emit()} - |)${type.extends.run { if (isEmpty()) "" else " : ${joinToString(", ") { it.emit() }}" }} - """.trimMargin() + override fun emit(type: Type, ast: AST) = + if (type.shape.value.isEmpty()) "${Spacer}data object ${type.emitName()}" + else """ + |data class ${type.emitName()}( + |${type.shape.emit()} + |)${type.extends.run { if (isEmpty()) "" else " : ${joinToString(", ") { it.emit() }}" }} + """.trimMargin() override fun Type.Shape.emit() = value.joinToString("\n") { "${Spacer}val ${it.emit()}," }.dropLast(1) - override fun Field.emit() = "${identifier.emit()}: ${reference.emit()}${if (isNullable) "?" else ""}" + override fun Field.emit() = "${identifier.emitVariableName()}: ${reference.emit()}${if (isNullable) "?" else ""}" override fun Reference.emit() = when (this) { is Reference.Unit -> "Unit" @@ -87,7 +89,13 @@ open class KotlinEmitter( .let { if (isDictionary) "Map" else it } - override fun Identifier.emit() = if (value in reservedKeywords) value.addBackticks() else value + override fun Identifier.emitClassName() = value + .firstToUpper() + + override fun Identifier.emitVariableName() = value + .sanitizeSymbol() + .firstToLower() + .let { if (value in reservedKeywords) it.addBackticks() else it} override fun emit(refined: Refined) = """ |data class ${refined.identifier.value.sanitizeSymbol()}(override val value: String): Wirespec.Refined { @@ -100,7 +108,7 @@ open class KotlinEmitter( override fun Refined.Validator.emit() = "Regex(\"\"\"${expression}\"\"\").matches(value)" override fun emit(enum: Enum) = """ - |enum class ${enum.identifier.value.sanitizeSymbol()} (val label: String): Wirespec.Enum { + |enum class ${enum.identifier.value.sanitizeSymbol()} (override val label: String): Wirespec.Enum { |${enum.entries.joinToString(",\n") { "${it.sanitizeEnum().sanitizeKeywords()}(\"$it\")" }.spacer()}; |${Spacer}override fun toString(): String { |${Spacer(2)}return label @@ -113,39 +121,39 @@ open class KotlinEmitter( """.trimMargin() override fun emit(channel: Channel) = """ - |interface ${channel.identifier.emit()}Channel { + |interface ${channel.identifier.emitClassName()}Channel { | operator fun invoke(message: ${channel.reference.emitWrap(channel.isNullable)}) |} """.trimMargin() override fun emit(endpoint: Endpoint) = """ - |object ${endpoint.identifier.emit()}Endpoint : Wirespec.Endpoint { + |object ${endpoint.identifier.emitClassName()}Endpoint : Wirespec.Endpoint { |${endpoint.pathParams.emitObject("Path", "Wirespec.Path") { it.emit() }} | |${endpoint.queries.emitObject("Queries", "Wirespec.Queries") { it.emit() }} | |${endpoint.headers.emitObject("Headers", "Wirespec.Request.Headers") { it.emit() }} | - |${endpoint.requests.joinToString("\n") { it.emit(endpoint) }} + |${endpoint.requests.first().emit(endpoint)} | |${Spacer}sealed interface Response : Wirespec.Response |${endpoint.emitResponseInterfaces()} | - |${endpoint.responses.joinToString("\n") { it.emit() }} + |${endpoint.responses.distinctBy { it.status }.joinToString("\n") { it.emit() }} | |${Spacer}fun toResponse(serialization: Wirespec.Serializer, response: Response<*>): Wirespec.RawResponse = |${Spacer(2)}when(response) { - |${endpoint.responses.joinToString("\n") { it.emitSerialized() }} + |${endpoint.responses.distinctBy { it.status }.joinToString("\n") { it.emitSerialized() }} |${Spacer(2)}} | |${Spacer}fun fromResponse(serialization: Wirespec.Deserializer, response: Wirespec.RawResponse): Response<*> = |${Spacer(2)}when (response.statusCode) { - |${endpoint.responses.joinToString("\n") { it.emitDeserialized() }} + |${endpoint.responses.filter { it.status.isStatusCode() }.distinctBy { it.status }.joinToString("\n") { it.emitDeserialized() }} |${Spacer(3)}else -> error("Cannot match response with status: ${'$'}{response.statusCode}") |${Spacer(2)}} | |${Spacer}interface Handler: Wirespec.Handler { - |${Spacer(2)}suspend fun ${endpoint.identifier.emit().firstToLower()}(request: Request): Response<*> + |${Spacer(2)}${emitHandleFunction(endpoint)} |${Spacer(2)}companion object: Wirespec.Server>, Wirespec.Client> { |${Spacer(3)}override val pathTemplate = "/${endpoint.path.joinToString("/") { it.emit() }}" |${Spacer(3)}override val method = "${endpoint.method}" @@ -162,6 +170,9 @@ open class KotlinEmitter( |} """.trimMargin() + open fun emitHandleFunction(endpoint: Endpoint): String = + "suspend fun ${endpoint.identifier.emitClassName().firstToLower()}(request: Request): Response<*>" + private fun Endpoint.emitResponseInterfaces() = responses .distinctBy { it.status[0] } .joinToString("\n") { "${Spacer}sealed interface Response${it.status[0]}XX : Response" } @@ -176,10 +187,10 @@ open class KotlinEmitter( fun Endpoint.Request.emit(endpoint: Endpoint) = """ |${Spacer}${emitConstructor(endpoint)} - |${Spacer(2)}override val path = Path${endpoint.pathParams.joinToString { it.identifier.emit() }.brace()} + |${Spacer(2)}override val path = Path${endpoint.pathParams.joinToString { it.identifier.emitVariableName() }.brace()} |${Spacer(2)}override val method = Wirespec.Method.${endpoint.method.name} - |${Spacer(2)}override val queries = Queries${endpoint.queries.joinToString { it.identifier.emit() }.brace()} - |${Spacer(2)}override val headers = Headers${endpoint.headers.joinToString { it.identifier.emit() }.brace()}${if (content == null) "\n${Spacer(2)}override val body = Unit" else ""} + |${Spacer(2)}override val queries = Queries${endpoint.queries.joinToString { it.identifier.emitVariableName() }.brace()} + |${Spacer(2)}override val headers = Headers${endpoint.headers.joinToString { it.identifier.emitVariableName() }.brace()}${if (content == null) "\n${Spacer(2)}override val body = Unit" else ""} |${Spacer}} | |${Spacer}fun toRequest(serialization: Wirespec.Serializer, request: Request): Wirespec.RawRequest = @@ -233,22 +244,22 @@ open class KotlinEmitter( """.trimMargin() private fun Field.emitSerialized(fields: String) = - """request.$fields.${identifier.emit()}?.let{"${identifier.emit()}" to serialization.serialize(it, typeOf<${reference.emit()}>())}""" + """request.$fields.${identifier.emitVariableName()}?.let{"${identifier.value}" to serialization.serialize(it, typeOf<${reference.emit()}>())}""" private fun IndexedValue.emitDeserialized() = - """${Spacer(3)}${value.identifier.emit()} = serialization.deserialize(request.path[${index}], typeOf<${value.reference.emit()}>())""" + """${Spacer(3)}${value.identifier.emitVariableName()} = serialization.deserialize(request.path[${index}], typeOf<${value.reference.emit()}>())""" private fun Field.emitDeserialized(fields: String) = if (isNullable) - """${Spacer(3)}${identifier.emit()} = request.$fields["${identifier.emit()}"]?.get(0)?.let{ serialization.deserialize(it, typeOf<${reference.emit()}>()) }""" + """${Spacer(3)}${identifier.emitVariableName()} = request.$fields["${identifier.value}"]?.let{ serialization.deserialize(it, typeOf<${reference.emit()}>()) }""" else - """${Spacer(3)}${identifier.emit()} = serialization.deserialize(requireNotNull(request.$fields["${identifier.emit()}"]) { "${identifier.emit()} is null" }, typeOf<${reference.emit()}>())""" + """${Spacer(3)}${identifier.emitVariableName()} = serialization.deserialize(requireNotNull(request.$fields["${identifier.value}"]) { "${identifier.emitVariableName()} is null" }, typeOf<${reference.emit()}>())""" - private fun Endpoint.Segment.Param.emitIdentifier() = "request.path.${identifier.value}.toString()" + private fun Endpoint.Segment.Param.emitIdentifier() = "request.path.${identifier.emitVariableName()}.let{serialization.serialize(it, typeOf<${reference.emit()}>())}" private fun Endpoint.Content?.emit() = this?.reference?.emit() ?: "Unit" - private fun Endpoint.Segment.Param.emit() = "${identifier.emit()}: ${reference.emit()}" + private fun Endpoint.Segment.Param.emit() = "${identifier.emitVariableName()}: ${reference.emit()}" private fun String.brace() = wrap("(", ")") private fun String.wrap(prefix: String, postfix: String) = if (isEmpty()) "" else "$prefix$this$postfix" @@ -284,7 +295,7 @@ open class KotlinEmitter( "in", "interface", "internal", "is", "null", "object", "open", "package", "return", "super", "this", "throw", "true", "try", "typealias", - "typeof", "val", "var", "when", "while" + "typeof", "val", "var", "when", "while", "private", "public" ) } diff --git a/src/compiler/core/src/commonMain/kotlin/community/flock/wirespec/compiler/core/emit/KotlinLegacyEmitter.kt b/src/compiler/core/src/commonMain/kotlin/community/flock/wirespec/compiler/core/emit/KotlinLegacyEmitter.kt index fe4bba16..70ff3e7f 100644 --- a/src/compiler/core/src/commonMain/kotlin/community/flock/wirespec/compiler/core/emit/KotlinLegacyEmitter.kt +++ b/src/compiler/core/src/commonMain/kotlin/community/flock/wirespec/compiler/core/emit/KotlinLegacyEmitter.kt @@ -39,12 +39,12 @@ open class KotlinLegacyEmitter( """.trimMargin() override fun Definition.emitName(): String = when (this) { - is Endpoint -> "${identifier.emit()}Endpoint" - is Channel -> "${identifier.emit()}Channel" - is Enum -> identifier.emit() - is Refined -> identifier.emit() - is Type -> identifier.emit() - is Union -> identifier.emit() + is Endpoint -> "${identifier.emitClassName()}Endpoint" + is Channel -> "${identifier.emitClassName()}Channel" + is Enum -> identifier.emitClassName() + is Refined -> identifier.emitClassName() + is Type -> identifier.emitClassName() + is Union -> identifier.emitClassName() } override fun notYetImplemented() = @@ -102,7 +102,7 @@ open class KotlinLegacyEmitter( override fun emit(channel: Channel): String = """ - |interface ${channel.identifier.emit()}Channel { + |interface ${channel.identifier.emitClassName()}Channel { | operator fun invoke(message: ${channel.reference.transform(channel.isNullable, false).emitWrap()}) |} """.trimMargin() diff --git a/src/compiler/core/src/commonMain/kotlin/community/flock/wirespec/compiler/core/emit/ScalaEmitter.kt b/src/compiler/core/src/commonMain/kotlin/community/flock/wirespec/compiler/core/emit/ScalaEmitter.kt index eab81331..fd7bdfdb 100644 --- a/src/compiler/core/src/commonMain/kotlin/community/flock/wirespec/compiler/core/emit/ScalaEmitter.kt +++ b/src/compiler/core/src/commonMain/kotlin/community/flock/wirespec/compiler/core/emit/ScalaEmitter.kt @@ -27,12 +27,12 @@ open class ScalaEmitter( ) : DefinitionModelEmitter, Emitter(logger) { override fun Definition.emitName(): String = when (this) { - is Endpoint -> "${identifier.emit()}Endpoint" - is Channel -> "${identifier.emit()}Channel" - is Enum -> identifier.emit() - is Refined -> identifier.emit() - is Type -> identifier.emit() - is Union -> identifier.emit() + is Endpoint -> "${identifier.emitClassName()}Endpoint" + is Channel -> "${identifier.emitClassName()}Channel" + is Enum -> identifier.emitClassName() + is Refined -> identifier.emitClassName() + is Type -> identifier.emitClassName() + is Union -> identifier.emitClassName() } override fun notYetImplemented() = @@ -53,9 +53,10 @@ open class ScalaEmitter( override fun Type.Shape.emit() = value.joinToString("\n") { it.emit() }.dropLast(1) override fun Field.emit() = - "${Spacer}val ${identifier.emit()}: ${if (isNullable) "Option[${reference.emit()}]" else reference.emit()}," + "${Spacer}val ${identifier.emitVariableName()}: ${if (isNullable) "Option[${reference.emit()}]" else reference.emit()}," - override fun Identifier.emit() = if (value in reservedKeywords) value.addBackticks() else value + override fun Identifier.emitClassName() = if (value in reservedKeywords) value.addBackticks() else value + override fun Identifier.emitVariableName() = if (value in reservedKeywords) value.addBackticks() else value override fun emit(channel: Channel) = notYetImplemented() @@ -77,26 +78,21 @@ open class ScalaEmitter( fun String.sanitize() = replace("-", "_").let { if (it.first().isDigit()) "_$it" else it } """ |sealed abstract class ${emitName()}(val label: String) - |object ${identifier.emit()} { - |${ - entries.joinToString("\n") { - """${Spacer}final case object ${ - it.sanitize().uppercase() - } extends ${identifier.emit()}(label = "$it")""" - } - } + |object ${identifier.emitClassName()} { + |${entries.joinToString("\n") { """${Spacer}final case object ${it.sanitize().uppercase()} extends ${identifier.emitClassName()}(label = "$it")""" }} |} |""".trimMargin() } - override fun emit(refined: Refined) = - """case class ${refined.emitName()}(val value: String) { - |${Spacer}implicit class ${refined.emitName()}Ops(val that: ${refined.emitName()}) { - |${refined.validator.emit()} - |${Spacer}} - |} - | - |""".trimMargin() + override fun emit(refined: Refined) = """ + |case class ${refined.emitName()}(val value: String) { + |${Spacer}implicit class ${refined.emitName()}Ops(val that: ${refined.emitName()}) { + |${refined.validator.emit()} + |${Spacer}} + |} + | + | + """.trimMargin() override fun Refined.Validator.emit() = diff --git a/src/compiler/core/src/commonMain/kotlin/community/flock/wirespec/compiler/core/emit/TypeScriptEmitter.kt b/src/compiler/core/src/commonMain/kotlin/community/flock/wirespec/compiler/core/emit/TypeScriptEmitter.kt index d48d99f6..cb8253c9 100644 --- a/src/compiler/core/src/commonMain/kotlin/community/flock/wirespec/compiler/core/emit/TypeScriptEmitter.kt +++ b/src/compiler/core/src/commonMain/kotlin/community/flock/wirespec/compiler/core/emit/TypeScriptEmitter.kt @@ -22,12 +22,12 @@ import community.flock.wirespec.compiler.utils.noLogger open class TypeScriptEmitter(logger: Logger = noLogger) : DefinitionModelEmitter, Emitter(logger) { override fun Definition.emitName(): String = when (this) { - is Endpoint -> identifier.emit() - is Enum -> identifier.emit() - is Refined -> identifier.emit() - is Type -> identifier.emit() - is Union -> identifier.emit() - is Channel -> identifier.emit() + is Endpoint -> identifier.emitClassName() + is Enum -> identifier.emitClassName() + is Refined -> identifier.emitClassName() + is Type -> identifier.emitClassName() + is Union -> identifier.emitClassName() + is Channel -> identifier.emitClassName() } override fun notYetImplemented() = @@ -58,10 +58,10 @@ open class TypeScriptEmitter(logger: Logger = noLogger) : DefinitionModelEmitter override fun Type.Shape.emit() = value.joinToString(",\n") { it.emit() } internal fun Endpoint.Segment.Param.emit() = - "${Spacer}\"${identifier.emit()}\": ${reference.emit()}" + "${Spacer}\"${identifier.emitVariableName()}\": ${reference.emit()}" override fun Field.emit() = - "${Spacer}\"${identifier.emit()}\"${if (isNullable) "?" else ""}: ${reference.emit()}" + "${Spacer}\"${identifier.emitVariableName()}\"${if (isNullable) "?" else ""}: ${reference.emit()}" override fun Reference.emit() = when (this) { is Reference.Unit -> "void" @@ -79,9 +79,9 @@ open class TypeScriptEmitter(logger: Logger = noLogger) : DefinitionModelEmitter override fun emit(refined: Refined) = """export type ${refined.identifier.sanitizeSymbol()} = string; - |const regExp${refined.identifier.emit()} = ${refined.validator.emit()}; - |export const validate${refined.identifier.emit()} = (value: string): value is ${refined.identifier.sanitizeSymbol()} => - |${Spacer}regExp${refined.identifier.emit()}.test(value); + |const regExp${refined.identifier.emitClassName()} = ${refined.validator.emit()}; + |export const validate${refined.identifier.emitClassName()} = (value: string): value is ${refined.identifier.sanitizeSymbol()} => + |${Spacer}regExp${refined.identifier.emitClassName()}.test(value); |""".trimMargin() override fun Refined.Validator.emit() = value @@ -98,7 +98,7 @@ open class TypeScriptEmitter(logger: Logger = noLogger) : DefinitionModelEmitter |${endpoint.requests.first().emitFunction(endpoint)} |${endpoint.responses.joinToString("\n") { it.emitFunction(endpoint) }} |${Spacer}export type Handler = { - |${Spacer(2)}${endpoint.identifier.sanitizeSymbol().firstToLower()}: (request:Request) => Promise + |${Spacer(2)}${emitHandleFunction(endpoint)} |${Spacer}} |${endpoint.emitClient().prependIndent(Spacer(1))} |${endpoint.emitServer().prependIndent(Spacer(1))} @@ -106,6 +106,9 @@ open class TypeScriptEmitter(logger: Logger = noLogger) : DefinitionModelEmitter | """.trimMargin() + fun emitHandleFunction(endpoint: Endpoint) = + "${endpoint.identifier.sanitizeSymbol().firstToLower()}: (request:Request) => Promise" + override fun emit(union: Union) = "export type ${union.identifier.sanitizeSymbol()} = ${union.entries.joinToString(" | ") { it.emit() }}\n" @@ -134,10 +137,10 @@ open class TypeScriptEmitter(logger: Logger = noLogger) : DefinitionModelEmitter private fun Endpoint.Request.emitFunction(endpoint: Endpoint) = """ |${Spacer}export const request = (${paramList(endpoint).takeIf { it.isNotEmpty() }?.let { "props: ${it.joinToObject { it.emit() }}" }.orEmpty()}): Request => ({ - |${Spacer(2)}path: ${endpoint.pathParams.joinToObject { "${it.identifier.emit()}: props.${it.identifier.emit()}" }}, + |${Spacer(2)}path: ${endpoint.pathParams.joinToObject { "${it.identifier.emitVariableName()}: props.${it.identifier.emitVariableName()}" }}, |${Spacer(2)}method: "${endpoint.method}", - |${Spacer(2)}queries: ${endpoint.queries.joinToObject { "${it.identifier.emit()}: props.${it.identifier.emit()}" }}, - |${Spacer(2)}headers: ${endpoint.headers.joinToObject { "${it.identifier.emit()}: props.${it.identifier.emit()}" }}, + |${Spacer(2)}queries: ${endpoint.queries.joinToObject { "${it.identifier.emitVariableName()}: props.${it.identifier.emitVariableName()}" }}, + |${Spacer(2)}headers: ${endpoint.headers.joinToObject { "${it.identifier.emitVariableName()}: props.${it.identifier.emitVariableName()}" }}, |${Spacer(2)}body: ${content?.let { "props.body" } ?: "undefined"}, |${Spacer}}) """.trimIndent() @@ -145,7 +148,7 @@ open class TypeScriptEmitter(logger: Logger = noLogger) : DefinitionModelEmitter private fun Endpoint.Response.emitFunction(endpoint: Endpoint) = """ |${Spacer}export const response${status.firstToUpper()} = (${paramList().takeIf { it.isNotEmpty() }?.let { "props: ${it.joinToObject { it.emit() }}" }.orEmpty()}): Response${status.firstToUpper()} => ({ |${Spacer(2)}status: ${status}, - |${Spacer(2)}headers: ${endpoint.headers.joinToObject { "${it.identifier.emit()}: props.${it.identifier.emit()}" }}, + |${Spacer(2)}headers: ${endpoint.headers.joinToObject { "${it.identifier.emitVariableName()}: props.${it.identifier.emitVariableName()}" }}, |${Spacer(2)}body: ${content?.let { "props.body" } ?: "undefined"}, |${Spacer}}) """.trimIndent() @@ -153,7 +156,7 @@ open class TypeScriptEmitter(logger: Logger = noLogger) : DefinitionModelEmitter private fun Iterable.joinToObject(transform: ((T) -> CharSequence)) = joinToString(", ", "{", "}", transform = transform) - private fun Param.emit() = "${identifier.emit()}${if (isNullable) "?" else ""}: ${reference.emit()}" + private fun Param.emit() = "${identifier.emitVariableName()}${if (isNullable) "?" else ""}: ${reference.emit()}" private fun Endpoint.Response.emitName() = "Response" + status.firstToUpper() @@ -250,11 +253,11 @@ open class TypeScriptEmitter(logger: Logger = noLogger) : DefinitionModelEmitter """.trimMargin() private fun IndexedValue.emitDeserialize() = - """${value.identifier.emit()}: serialization.deserialize(request.path[${index}])""" + """${value.identifier.emitVariableName()}: serialization.deserialize(request.path[${index}])""" private fun Field.emitDeserialize(fields: String) = - """${identifier.emit()}: serialization.deserialize(request.$fields.${identifier.emit()})""" + """${identifier.emitVariableName()}: serialization.deserialize(request.$fields.${identifier.emitVariableName()})""" private fun Field.emitSerialize(fields: String) = - """${identifier.emit()}: serialization.serialize(request.$fields.${identifier.emit()})""" + """${identifier.emitVariableName()}: serialization.serialize(request.$fields.${identifier.emitVariableName()})""" } diff --git a/src/compiler/core/src/commonMain/kotlin/community/flock/wirespec/compiler/core/emit/WirespecEmitter.kt b/src/compiler/core/src/commonMain/kotlin/community/flock/wirespec/compiler/core/emit/WirespecEmitter.kt index e8065f7b..1d6d17e2 100644 --- a/src/compiler/core/src/commonMain/kotlin/community/flock/wirespec/compiler/core/emit/WirespecEmitter.kt +++ b/src/compiler/core/src/commonMain/kotlin/community/flock/wirespec/compiler/core/emit/WirespecEmitter.kt @@ -22,18 +22,18 @@ import community.flock.wirespec.compiler.utils.noLogger open class WirespecEmitter(logger: Logger = noLogger) : DefinitionModelEmitter, Emitter(logger) { override fun Definition.emitName(): String = when (this) { - is Endpoint -> identifier.emit() - is Enum -> identifier.emit() - is Refined -> identifier.emit() - is Type -> identifier.emit() - is Union -> identifier.emit() - is Channel -> identifier.emit() + is Endpoint -> identifier.emitClassName() + is Enum -> identifier.emitClassName() + is Refined -> identifier.emitClassName() + is Type -> identifier.emitClassName() + is Union -> identifier.emitClassName() + is Channel -> identifier.emitClassName() } override fun notYetImplemented() = "\n" override fun emit(type: Type, ast: AST) = """ - |type ${type.identifier.emit()} { + |type ${type.identifier.emitClassName()} { |${type.shape.emit()} |} |""".trimMargin() @@ -41,11 +41,12 @@ open class WirespecEmitter(logger: Logger = noLogger) : DefinitionModelEmitter, override fun Type.Shape.emit() = value.joinToString(",\n") { "$Spacer${it.emit()}" } - override fun Field.emit() = "${identifier.emit()}: ${reference.emit()}${if (isNullable) "?" else ""}" + override fun Field.emit() = "${identifier.emitVariableName()}: ${reference.emit()}${if (isNullable) "?" else ""}" - override fun Identifier.emit() = if (value in reservedKeywords) value.addBackticks() else value + override fun Identifier.emitVariableName() = if (value in reservedKeywords) value.addBackticks() else value + override fun Identifier.emitClassName() = if (value in reservedKeywords) value.addBackticks() else value - override fun emit(channel: Channel): String = "channel ${channel.identifier.emit()} -> ${channel.reference.emit()}" + override fun emit(channel: Channel): String = "channel ${channel.identifier.emitClassName()} -> ${channel.reference.emit()}" override fun Reference.emit(): String = when (this) { is Reference.Unit -> "Unit" @@ -62,21 +63,21 @@ open class WirespecEmitter(logger: Logger = noLogger) : DefinitionModelEmitter, .let { if (isDictionary) "{ $it }" else it } override fun emit(enum: Enum) = - "enum ${enum.identifier.emit()} {\n${Spacer}${enum.entries.joinToString(", ") { it.capitalize() }}\n}\n" + "enum ${enum.identifier.emitClassName()} {\n${Spacer}${enum.entries.joinToString(", ") { it.capitalize() }}\n}\n" - override fun emit(refined: Refined) = "type ${refined.identifier.emit()} ${refined.validator.emit()}\n" + override fun emit(refined: Refined) = "type ${refined.identifier.emitClassName()} ${refined.validator.emit()}\n" override fun Refined.Validator.emit() = value override fun emit(endpoint: Endpoint) = """ - |endpoint ${endpoint.identifier.emit()} ${endpoint.method}${endpoint.requests.emitRequest()} ${endpoint.path.emitPath()}${endpoint.queries.emitQuery()} -> { + |endpoint ${endpoint.identifier.emitClassName()} ${endpoint.method}${endpoint.requests.emitRequest()} ${endpoint.path.emitPath()}${endpoint.queries.emitQuery()} -> { |${endpoint.responses.joinToString("\n") { "$Spacer${it.status.fixStatus()} -> ${it.content?.reference?.emit() ?: "Unit"}${if (it.content?.isNullable == true) "?" else ""}" }} |} | """.trimMargin() override fun emit(union: Union) = - "type ${union.identifier.emit()} = ${union.entries.joinToString(" | ") { it.emit() }}\n" + "type ${union.identifier.emitClassName()} = ${union.entries.joinToString(" | ") { it.emit() }}\n" private fun String.fixStatus(): String = when (this) { "default" -> "200" diff --git a/src/compiler/core/src/commonMain/kotlin/community/flock/wirespec/compiler/core/emit/common/Emitter.kt b/src/compiler/core/src/commonMain/kotlin/community/flock/wirespec/compiler/core/emit/common/Emitter.kt index d2bd7665..989f07e8 100644 --- a/src/compiler/core/src/commonMain/kotlin/community/flock/wirespec/compiler/core/emit/common/Emitter.kt +++ b/src/compiler/core/src/commonMain/kotlin/community/flock/wirespec/compiler/core/emit/common/Emitter.kt @@ -60,12 +60,18 @@ abstract class Emitter( } } - internal fun Endpoint.Segment.emit() = + fun Endpoint.Segment.emit() = when (this) { is Endpoint.Segment.Literal -> value is Endpoint.Segment.Param -> "{${identifier.value}}" } + internal fun Endpoint.Segment.emitMap() = + when (this) { + is Endpoint.Segment.Literal -> value + is Endpoint.Segment.Param -> "${'$'}{props.${identifier.emitClassName()}}" + } + internal val Endpoint.pathParams get() = path.filterIsInstance() internal val Endpoint.indexedPathParams diff --git a/src/compiler/core/src/commonMain/kotlin/community/flock/wirespec/compiler/core/emit/common/Emitters.kt b/src/compiler/core/src/commonMain/kotlin/community/flock/wirespec/compiler/core/emit/common/Emitters.kt index b92a39dd..e0c09e78 100644 --- a/src/compiler/core/src/commonMain/kotlin/community/flock/wirespec/compiler/core/emit/common/Emitters.kt +++ b/src/compiler/core/src/commonMain/kotlin/community/flock/wirespec/compiler/core/emit/common/Emitters.kt @@ -43,5 +43,6 @@ interface ChannelDefinitionEmitter { } interface IdentifierEmitter { - fun Identifier.emit(): String = value + fun Identifier.emitClassName(): String = value + fun Identifier.emitVariableName(): String = value } diff --git a/src/compiler/core/src/commonMain/kotlin/community/flock/wirespec/compiler/core/emit/shared/KotlinShared.kt b/src/compiler/core/src/commonMain/kotlin/community/flock/wirespec/compiler/core/emit/shared/KotlinShared.kt index 5d604f4b..91247766 100644 --- a/src/compiler/core/src/commonMain/kotlin/community/flock/wirespec/compiler/core/emit/shared/KotlinShared.kt +++ b/src/compiler/core/src/commonMain/kotlin/community/flock/wirespec/compiler/core/emit/shared/KotlinShared.kt @@ -12,7 +12,7 @@ data object KotlinShared : Shared { |import kotlin.reflect.KType | |object Wirespec { - |${Spacer}interface Enum + |${Spacer}interface Enum { val label: String } |${Spacer}interface Endpoint |${Spacer}interface Refined { val value: String } |${Spacer}interface Path diff --git a/src/compiler/core/src/commonTest/kotlin/community/flock/wirespec/compiler/core/CompileEnumTest.kt b/src/compiler/core/src/commonTest/kotlin/community/flock/wirespec/compiler/core/CompileEnumTest.kt index 98050c9e..216ca45f 100644 --- a/src/compiler/core/src/commonTest/kotlin/community/flock/wirespec/compiler/core/CompileEnumTest.kt +++ b/src/compiler/core/src/commonTest/kotlin/community/flock/wirespec/compiler/core/CompileEnumTest.kt @@ -24,7 +24,7 @@ class CompileEnumTest { |import community.flock.wirespec.kotlin.Wirespec |import kotlin.reflect.typeOf | - |enum class MyAwesomeEnum (val label: String): Wirespec.Enum { + |enum class MyAwesomeEnum (override val label: String): Wirespec.Enum { | ONE("ONE"), | Two("Two"), | THREE_MORE("THREE_MORE"); diff --git a/src/compiler/core/src/commonTest/kotlin/community/flock/wirespec/compiler/core/CompileFullEndpointTest.kt b/src/compiler/core/src/commonTest/kotlin/community/flock/wirespec/compiler/core/CompileFullEndpointTest.kt index d085d187..b29ed0a9 100644 --- a/src/compiler/core/src/commonTest/kotlin/community/flock/wirespec/compiler/core/CompileFullEndpointTest.kt +++ b/src/compiler/core/src/commonTest/kotlin/community/flock/wirespec/compiler/core/CompileFullEndpointTest.kt @@ -70,7 +70,7 @@ class CompileFullEndpointTest { | | fun toRequest(serialization: Wirespec.Serializer, request: Request): Wirespec.RawRequest = | Wirespec.RawRequest( - | path = listOf("todos", request.path.id.toString()), + | path = listOf("todos", request.path.id.let{serialization.serialize(it, typeOf())}), | method = request.method.name, | queries = listOf(request.queries.done?.let{"done" to serialization.serialize(it, typeOf())}).filterNotNull().toMap(), | headers = listOf(request.headers.token?.let{"token" to serialization.serialize(it, typeOf())}).filterNotNull().toMap(), diff --git a/src/compiler/core/src/commonTest/kotlin/community/flock/wirespec/compiler/core/emit/KotlinEmitterTest.kt b/src/compiler/core/src/commonTest/kotlin/community/flock/wirespec/compiler/core/emit/KotlinEmitterTest.kt index 433cd1e3..4f722264 100644 --- a/src/compiler/core/src/commonTest/kotlin/community/flock/wirespec/compiler/core/emit/KotlinEmitterTest.kt +++ b/src/compiler/core/src/commonTest/kotlin/community/flock/wirespec/compiler/core/emit/KotlinEmitterTest.kt @@ -56,7 +56,7 @@ class KotlinEmitterTest { |import community.flock.wirespec.kotlin.Wirespec |import kotlin.reflect.typeOf | - |enum class TodoStatus (val label: String): Wirespec.Enum { + |enum class TodoStatus (override val label: String): Wirespec.Enum { | OPEN("OPEN"), | IN_PROGRESS("IN_PROGRESS"), | CLOSE("CLOSE"); diff --git a/src/compiler/lib/build.gradle.kts b/src/compiler/lib/build.gradle.kts index f5f656cc..fba3c008 100644 --- a/src/compiler/lib/build.gradle.kts +++ b/src/compiler/lib/build.gradle.kts @@ -43,7 +43,7 @@ kotlin { } val jsMain by getting { dependencies { - implementation(kotlin("test")) + implementation(libs.kotlin.test) implementation(libs.kotlinx.resources) } } diff --git a/src/converter/openapi/build.gradle.kts b/src/converter/openapi/build.gradle.kts index 350d1ac0..5a58a706 100644 --- a/src/converter/openapi/build.gradle.kts +++ b/src/converter/openapi/build.gradle.kts @@ -37,7 +37,7 @@ kotlin { commonTest { dependencies { implementation(libs.kotlinx.resources) - implementation(kotlin("test")) + implementation(libs.kotlin.test) implementation(libs.bundles.kotest) } } diff --git a/src/converter/openapi/src/commonMain/kotlin/community/flock/wirespec/openapi/v3/OpenApiV3Parser.kt b/src/converter/openapi/src/commonMain/kotlin/community/flock/wirespec/openapi/v3/OpenApiV3Parser.kt index c4783aa9..e1258909 100644 --- a/src/converter/openapi/src/commonMain/kotlin/community/flock/wirespec/openapi/v3/OpenApiV3Parser.kt +++ b/src/converter/openapi/src/commonMain/kotlin/community/flock/wirespec/openapi/v3/OpenApiV3Parser.kt @@ -51,7 +51,7 @@ object OpenApiV3Parser { .flatMap { (key, path) -> path.toOperationList().map { (method, operation) -> val parameters = resolveParameters(path) + resolveParameters(operation) - val segments = toSegments(key, parameters) + val segments = toSegments(key, parameters, operation, method) val name = operation.toName() ?: (key.toName() + method.name) val query = parameters .filter { it.`in` == ParameterLocation.QUERY } @@ -208,12 +208,12 @@ object OpenApiV3Parser { } } - private fun OpenAPIObject.toSegments(path: Path, parameters: List) = + private fun OpenAPIObject.toSegments(path: Path, parameters: List, operation: OperationObject, method: Endpoint.Method) = path.value.split("/").drop(1).filter { it.isNotBlank() }.map { segment -> when (segment.isParam()) { true -> { val param = segment.substring(1, segment.length - 1) - val name = path.toName() + val name = operation.toName() ?: (path.toName() + method.name) parameters .find { it.name == param } ?.schema diff --git a/src/integration/jackson/build.gradle.kts b/src/integration/jackson/build.gradle.kts index 1cc62020..78d3f1c3 100644 --- a/src/integration/jackson/build.gradle.kts +++ b/src/integration/jackson/build.gradle.kts @@ -29,9 +29,7 @@ kotlin { commonTest { dependencies { implementation(project(":src:integration:wirespec")) - implementation(kotlin("test-common")) - implementation(kotlin("test-annotations-common")) - implementation(kotlin("test-junit")) + implementation(libs.bundles.kotlin.test) } } val jvmMain by getting { diff --git a/src/integration/jackson/src/jvmTest/kotlin/community/flock/wirespec/integration/jackson/kotlin/generated/Todo.kt b/src/integration/jackson/src/jvmTest/kotlin/community/flock/wirespec/integration/jackson/kotlin/generated/Todo.kt index a3a18ea3..2be1e2c0 100644 --- a/src/integration/jackson/src/jvmTest/kotlin/community/flock/wirespec/integration/jackson/kotlin/generated/Todo.kt +++ b/src/integration/jackson/src/jvmTest/kotlin/community/flock/wirespec/integration/jackson/kotlin/generated/Todo.kt @@ -22,7 +22,7 @@ data class Error( val code: String, val description: String ) -enum class TodoCategory (val label: String): Wirespec.Enum { +enum class TodoCategory (override val label: String): Wirespec.Enum { WORK("WORK"), LIFE("LIFE"); override fun toString(): String { diff --git a/src/integration/spring/README.md b/src/integration/spring/README.md new file mode 100644 index 00000000..7c979cbe --- /dev/null +++ b/src/integration/spring/README.md @@ -0,0 +1,61 @@ +# Spring integration lib + +This module offers Spring configuration which binds Wirespec endpoints as request mappings. + +## Install + +```xml + + community.flock.wirespec.integration + spring-jvm + {VERSION} + +``` + +## Usage +Use the custom Java or Kotlin spring emitters to generate wirespec spring enabled endpoint interfaces +- [SpringJavaEmitter.kt](src%2FjvmMain%2Fkotlin%2Fcommunity%2Fflock%2Fwirespec%2Fintegration%2Fspring%2Femit%2FSpringJavaEmitter.kt) +- [SpringKotlinEmitter.kt](src%2FjvmMain%2Fkotlin%2Fcommunity%2Fflock%2Fwirespec%2Fintegration%2Fspring%2Femit%2FSpringKotlinEmitter.kt) + +Load the wirspec spring configuration +- [WirespecConfiguration.kt](src%2FjvmMain%2Fkotlin%2Fcommunity%2Fflock%2Fwirespec%2Fintegration%2Fspring%2Fconfiguration%2FWirespecConfiguration.kt) + +```java +@SpringBootApplication +@Import(WirespecConfiguration.class) +public class TodoApplication { + public static void main(String[] args) { + SpringApplication.run(TodoApplication.class, args); + } +} + +``` + +```java +@RestController +class TodoController implements GetTodosEndpoint { + + private final TodoService service; + + public TodoController(TodoService service) { + this.service = service; + } + + @Override + public CompletableFuture> createTodo(CreateTodoEndpoint.Request request) { + var todoInput = switch (request) { + case CreateTodoEndpoint.RequestApplicationJson req -> req.getContent().body(); + }; + var todo = new Todo( + UUID.randomUUID().toString(), + todoInput.name(), + todoInput.done() + ); + service.create(todo); + var res = new CreateTodoEndpoint.Response200ApplicationJson(Map.of(), todo); + return CompletableFuture.completedFuture(res); + } +} +``` + +For a more extensive example go to [Spring integration example](examples/spring-boot-integration) diff --git a/src/integration/spring/build.gradle.kts b/src/integration/spring/build.gradle.kts new file mode 100644 index 00000000..03501830 --- /dev/null +++ b/src/integration/spring/build.gradle.kts @@ -0,0 +1,56 @@ +plugins { + alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.kotlinx.resources) + alias(libs.plugins.spring.boot) + alias(libs.plugins.spring.dependency.management) +} + +group = "${libs.versions.group.id.get()}.integration" +version = System.getenv(libs.versions.from.env.get()) ?: libs.versions.default.get() + +repositories { + mavenCentral() + maven(uri("https://s01.oss.sonatype.org/service/local/repo_groups/public/content")) +} + +kotlin { + jvm { + testRuns["test"].executionTask.configure { + useJUnitPlatform() + } + withJava() + java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(libs.versions.java.get())) + } + } + } + sourceSets { + commonTest { + dependencies { + implementation(project(":src:integration:wirespec")) + } + } + val jvmMain by getting { + dependencies { + compileOnly(project(":src:compiler:core")) + compileOnly(project(":src:integration:wirespec")) + implementation(project(":src:integration:jackson")) + implementation(libs.jackson.kotlin) + implementation(libs.kotlin.reflect) + implementation(libs.kotlinx.coroutines.reactor) + implementation(libs.spring.boot.web) + runtimeOnly(libs.junit.launcher) + } + } + val jvmTest by getting { + dependencies { + implementation(project(":src:compiler:core")) + implementation(project(":src:converter:openapi")) + implementation(project(":src:integration:wirespec")) + implementation(libs.spring.boot.test) + implementation(libs.kotlin.junit) + } + } + } +} diff --git a/src/integration/spring/src/jvmMain/kotlin/community/flock/wirespec/integration/spring/java/Extensions.kt b/src/integration/spring/src/jvmMain/kotlin/community/flock/wirespec/integration/spring/java/Extensions.kt new file mode 100644 index 00000000..2dd9d6b3 --- /dev/null +++ b/src/integration/spring/src/jvmMain/kotlin/community/flock/wirespec/integration/spring/java/Extensions.kt @@ -0,0 +1,29 @@ +package community.flock.wirespec.integration.spring.java + +import community.flock.wirespec.java.Wirespec +import java.io.BufferedReader +import java.lang.reflect.Method +import java.util.function.Function +import kotlin.reflect.full.companionObjectInstance + +fun Class<*>.invoke( + method: Method, + wirespecSerialization: Wirespec.Serialization, + request: Wirespec.Request +): Wirespec.Request<*> = if (isKotlinClass()) { + val func = method( + kotlin.companionObjectInstance, + wirespecSerialization + ) as (Wirespec.Request) -> Wirespec.Request<*> + func(request) +} else { + val func = method( + this, + wirespecSerialization + ) as Function, Wirespec.Request<*>> + func.apply(request) +} + +private fun Class<*>.isKotlinClass(): Boolean = declaredAnnotations.any { + it.annotationClass.qualifiedName == "kotlin.Metadata" +} diff --git a/src/integration/spring/src/jvmMain/kotlin/community/flock/wirespec/integration/spring/java/configuration/WirespecConfiguration.kt b/src/integration/spring/src/jvmMain/kotlin/community/flock/wirespec/integration/spring/java/configuration/WirespecConfiguration.kt new file mode 100644 index 00000000..44fc5788 --- /dev/null +++ b/src/integration/spring/src/jvmMain/kotlin/community/flock/wirespec/integration/spring/java/configuration/WirespecConfiguration.kt @@ -0,0 +1,28 @@ +package community.flock.wirespec.integration.spring.java.configuration + +import com.fasterxml.jackson.databind.ObjectMapper +import community.flock.wirespec.integration.jackson.java.WirespecModuleJava +import community.flock.wirespec.integration.spring.java.web.WirespecResponseBodyAdvice +import community.flock.wirespec.java.Wirespec +import java.lang.reflect.Type +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.context.annotation.Import + +@Configuration +@Import(WirespecResponseBodyAdvice::class, WirespecWebMvcConfiguration::class) +open class WirespecConfiguration { + + @Bean + open fun wirespecSerialization(objectMapper: ObjectMapper) = object : Wirespec.Serialization { + + private val wirespecObjectMapper = objectMapper.copy().registerModule(WirespecModuleJava()) + + override fun serialize(body: T, type: Type?): String = wirespecObjectMapper.writeValueAsString(body) + + override fun deserialize(raw: String?, valueType: Type?): T? = raw?.let { + val type = wirespecObjectMapper.constructType(valueType) + wirespecObjectMapper.readValue(it, type) + } + } +} diff --git a/src/integration/spring/src/jvmMain/kotlin/community/flock/wirespec/integration/spring/java/configuration/WirespecWebMvcConfiguration.kt b/src/integration/spring/src/jvmMain/kotlin/community/flock/wirespec/integration/spring/java/configuration/WirespecWebMvcConfiguration.kt new file mode 100644 index 00000000..158d38de --- /dev/null +++ b/src/integration/spring/src/jvmMain/kotlin/community/flock/wirespec/integration/spring/java/configuration/WirespecWebMvcConfiguration.kt @@ -0,0 +1,20 @@ +package community.flock.wirespec.integration.spring.java.configuration + +import community.flock.wirespec.integration.spring.java.web.WirespecMethodArgumentResolver +import community.flock.wirespec.java.Wirespec +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.context.annotation.Configuration +import org.springframework.web.method.support.HandlerMethodArgumentResolver +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer + +@Configuration +open class WirespecWebMvcConfiguration : WebMvcConfigurer { + + @Autowired + lateinit var wirespecSerialization: Wirespec.Serialization + + override fun addArgumentResolvers(argumentResolvers: MutableList) { + argumentResolvers.add(WirespecMethodArgumentResolver(wirespecSerialization)) + } + +} diff --git a/src/integration/spring/src/jvmMain/kotlin/community/flock/wirespec/integration/spring/java/emit/SpringJavaEmitter.kt b/src/integration/spring/src/jvmMain/kotlin/community/flock/wirespec/integration/spring/java/emit/SpringJavaEmitter.kt new file mode 100644 index 00000000..0cf2585a --- /dev/null +++ b/src/integration/spring/src/jvmMain/kotlin/community/flock/wirespec/integration/spring/java/emit/SpringJavaEmitter.kt @@ -0,0 +1,22 @@ +package community.flock.wirespec.integration.spring.java.emit + +import community.flock.wirespec.compiler.core.emit.JavaEmitter +import community.flock.wirespec.compiler.core.parse.Endpoint +import community.flock.wirespec.compiler.utils.Logger + +class SpringJavaEmitter(packageName: String, logger: Logger) : JavaEmitter(packageName, logger) { + override fun emitHandleFunction(endpoint: Endpoint): String { + val path = "/${endpoint.path.joinToString("/") { it.emit() }}" + val annotation = when (endpoint.method) { + Endpoint.Method.GET -> "@org.springframework.web.bind.annotation.GetMapping(\"${path}\")\n" + Endpoint.Method.POST -> "@org.springframework.web.bind.annotation.PostMapping(\"${path}\")\n" + Endpoint.Method.PUT -> "@org.springframework.web.bind.annotation.PutMapping(\"${path}\")\n" + Endpoint.Method.DELETE -> "@org.springframework.web.bind.annotation.DeleteMapping(\"${path}\")\n" + else -> error("Method not implemented: ${endpoint.method}") + } + return """ + |${annotation} + |${super.emitHandleFunction(endpoint)} + """.trimMargin() + } +} \ No newline at end of file diff --git a/src/integration/spring/src/jvmMain/kotlin/community/flock/wirespec/integration/spring/java/web/WirespecMethodArgumentResolver.kt b/src/integration/spring/src/jvmMain/kotlin/community/flock/wirespec/integration/spring/java/web/WirespecMethodArgumentResolver.kt new file mode 100644 index 00000000..559b343a --- /dev/null +++ b/src/integration/spring/src/jvmMain/kotlin/community/flock/wirespec/integration/spring/java/web/WirespecMethodArgumentResolver.kt @@ -0,0 +1,47 @@ +package community.flock.wirespec.integration.spring.java.web + +import community.flock.wirespec.java.Wirespec +import jakarta.servlet.http.HttpServletRequest +import java.util.stream.Collectors +import org.springframework.core.MethodParameter +import org.springframework.web.bind.support.WebDataBinderFactory +import org.springframework.web.context.request.NativeWebRequest +import org.springframework.web.method.support.HandlerMethodArgumentResolver +import org.springframework.web.method.support.ModelAndViewContainer + +class WirespecMethodArgumentResolver( + private val wirespecSerialization: Wirespec.Serialization +) : HandlerMethodArgumentResolver { + + override fun supportsParameter(parameter: MethodParameter): Boolean = + Wirespec.Request::class.java.isAssignableFrom(parameter.parameterType) + + override fun resolveArgument( + parameter: MethodParameter, + mavContainer: ModelAndViewContainer?, + webRequest: NativeWebRequest, + binderFactory: WebDataBinderFactory? + ): Wirespec.Request<*> { + val servletRequest = webRequest.nativeRequest as HttpServletRequest + val declaringClass = parameter.parameterType.declaringClass + val handler = declaringClass.declaredClasses.toList().find { it.simpleName == "Handler" } + val handlers = handler?.declaredClasses?.toList()?.find { it.simpleName == "Handlers" } + val instance = handlers?.getDeclaredConstructor()?.newInstance() as Wirespec.Server<*, *> + val server = instance.getServer(wirespecSerialization) + return server.from(servletRequest.toRawRequest()) + } +} + +fun HttpServletRequest.toRawRequest(): Wirespec.RawRequest = Wirespec.RawRequest( + method, + pathInfo.split("/"), + queryString + ?.split("&") + ?.associate { + val (key, value) = it.split("=") + key to value + } + .orEmpty(), + headerNames.toList().associateWith(::getHeader), + reader.lines().collect(Collectors.joining(System.lineSeparator())) +) diff --git a/src/integration/spring/src/jvmMain/kotlin/community/flock/wirespec/integration/spring/java/web/WirespecResponseBodyAdvice.kt b/src/integration/spring/src/jvmMain/kotlin/community/flock/wirespec/integration/spring/java/web/WirespecResponseBodyAdvice.kt new file mode 100644 index 00000000..1f9e6d6d --- /dev/null +++ b/src/integration/spring/src/jvmMain/kotlin/community/flock/wirespec/integration/spring/java/web/WirespecResponseBodyAdvice.kt @@ -0,0 +1,45 @@ +package community.flock.wirespec.integration.spring.java.web + +import com.fasterxml.jackson.databind.ObjectMapper +import community.flock.wirespec.java.Wirespec +import org.springframework.core.MethodParameter +import org.springframework.http.HttpStatusCode +import org.springframework.http.MediaType +import org.springframework.http.converter.HttpMessageConverter +import org.springframework.http.server.ServerHttpRequest +import org.springframework.http.server.ServerHttpResponse +import org.springframework.web.bind.annotation.ControllerAdvice +import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice + +@ControllerAdvice +class WirespecResponseBodyAdvice( + private val objectMapper: ObjectMapper, + private val wirespecSerialization: Wirespec.Serialization +) : ResponseBodyAdvice { + + override fun supports(returnType: MethodParameter, converterType: Class?>): Boolean = + Wirespec.Response::class.java.isAssignableFrom(returnType.parameterType) + + override fun beforeBodyWrite( + body: Any?, + returnType: MethodParameter, + selectedContentType: MediaType, + selectedConverterType: Class>, + request: ServerHttpRequest, + response: ServerHttpResponse + ): Any? { + val declaringClass = returnType.parameterType.declaringClass + val handler = declaringClass.declaredClasses.toList().find { it.simpleName == "Handler" } + val handlers = handler?.declaredClasses?.toList()?.find { it.simpleName == "Handlers" } ?: error("Handlers not found") + val instance = handlers.getDeclaredConstructor().newInstance() as Wirespec.Server, Wirespec.Response<*>> + val server = instance.getServer(wirespecSerialization) + return when (body) { + is Wirespec.Response<*> -> { + val rawResponse = server.to(body) + response.setStatusCode(HttpStatusCode.valueOf(rawResponse.statusCode)) + rawResponse.body?.let { objectMapper.readTree(it) } + } + else -> body + } + } +} diff --git a/src/integration/spring/src/jvmMain/kotlin/community/flock/wirespec/integration/spring/kotlin/configuration/WirespecConfiguration.kt b/src/integration/spring/src/jvmMain/kotlin/community/flock/wirespec/integration/spring/kotlin/configuration/WirespecConfiguration.kt new file mode 100644 index 00000000..5410db06 --- /dev/null +++ b/src/integration/spring/src/jvmMain/kotlin/community/flock/wirespec/integration/spring/kotlin/configuration/WirespecConfiguration.kt @@ -0,0 +1,29 @@ +package community.flock.wirespec.integration.spring.kotlin.configuration + +import com.fasterxml.jackson.databind.ObjectMapper +import community.flock.wirespec.integration.jackson.kotlin.WirespecModuleKotlin +import community.flock.wirespec.integration.spring.kotlin.web.WirespecResponseBodyAdvice +import community.flock.wirespec.kotlin.Wirespec +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.context.annotation.Import +import kotlin.reflect.KType +import kotlin.reflect.javaType + +@Configuration +@OptIn(ExperimentalStdlibApi::class) +@Import(WirespecResponseBodyAdvice::class, WirespecWebMvcConfiguration::class) +open class WirespecConfiguration { + + @Bean + open fun wirespecSerialization(objectMapper: ObjectMapper) = object : Wirespec.Serialization { + + private val wirespecObjectMapper = objectMapper.copy().registerModule(WirespecModuleKotlin()) + + override fun serialize(t: T, kType: KType): String = wirespecObjectMapper.writeValueAsString(t) + + override fun deserialize(raw: String, kType: KType): T = wirespecObjectMapper + .constructType(kType.javaType) + .let { wirespecObjectMapper.readValue(raw, it) } + } +} diff --git a/src/integration/spring/src/jvmMain/kotlin/community/flock/wirespec/integration/spring/kotlin/configuration/WirespecWebMvcConfiguration.kt b/src/integration/spring/src/jvmMain/kotlin/community/flock/wirespec/integration/spring/kotlin/configuration/WirespecWebMvcConfiguration.kt new file mode 100644 index 00000000..d2941e40 --- /dev/null +++ b/src/integration/spring/src/jvmMain/kotlin/community/flock/wirespec/integration/spring/kotlin/configuration/WirespecWebMvcConfiguration.kt @@ -0,0 +1,19 @@ +package community.flock.wirespec.integration.spring.kotlin.configuration + +import community.flock.wirespec.integration.spring.kotlin.web.WirespecMethodArgumentResolver +import community.flock.wirespec.kotlin.Wirespec +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.context.annotation.Configuration +import org.springframework.web.method.support.HandlerMethodArgumentResolver +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer + +@Configuration +open class WirespecWebMvcConfiguration : WebMvcConfigurer { + + @Autowired + lateinit var wirespecSerialization: Wirespec.Serialization + + override fun addArgumentResolvers(argumentResolvers: MutableList) { + argumentResolvers.add(WirespecMethodArgumentResolver(wirespecSerialization)) + } +} diff --git a/src/integration/spring/src/jvmMain/kotlin/community/flock/wirespec/integration/spring/kotlin/emit/SpringKotlinEmitter.kt b/src/integration/spring/src/jvmMain/kotlin/community/flock/wirespec/integration/spring/kotlin/emit/SpringKotlinEmitter.kt new file mode 100644 index 00000000..b9e16bf1 --- /dev/null +++ b/src/integration/spring/src/jvmMain/kotlin/community/flock/wirespec/integration/spring/kotlin/emit/SpringKotlinEmitter.kt @@ -0,0 +1,23 @@ +package community.flock.wirespec.integration.spring.kotlin.emit + +import community.flock.wirespec.compiler.core.emit.KotlinEmitter +import community.flock.wirespec.compiler.core.parse.Endpoint +import community.flock.wirespec.compiler.utils.Logger +import community.flock.wirespec.compiler.utils.noLogger + +class SpringKotlinEmitter(packageName: String, logger: Logger = noLogger) : KotlinEmitter(packageName, logger) { + override fun emitHandleFunction(endpoint: Endpoint): String { + val path = "/${endpoint.path.joinToString("/") { it.emit() }}" + val annotation = when (endpoint.method) { + Endpoint.Method.GET -> "@org.springframework.web.bind.annotation.GetMapping(\"${path}\")\n" + Endpoint.Method.POST -> "@org.springframework.web.bind.annotation.PostMapping(\"${path}\")\n" + Endpoint.Method.PUT -> "@org.springframework.web.bind.annotation.PutMapping(\"${path}\")\n" + Endpoint.Method.DELETE -> "@org.springframework.web.bind.annotation.DeleteMapping(\"${path}\")\n" + else -> error("Method not implemented: ${endpoint.method}") + } + return """ + |${annotation} + |${super.emitHandleFunction(endpoint)} + """.trimMargin() + } +} diff --git a/src/integration/spring/src/jvmMain/kotlin/community/flock/wirespec/integration/spring/kotlin/web/WirespecMethodArgumentResolver.kt b/src/integration/spring/src/jvmMain/kotlin/community/flock/wirespec/integration/spring/kotlin/web/WirespecMethodArgumentResolver.kt new file mode 100644 index 00000000..d55cd1b7 --- /dev/null +++ b/src/integration/spring/src/jvmMain/kotlin/community/flock/wirespec/integration/spring/kotlin/web/WirespecMethodArgumentResolver.kt @@ -0,0 +1,49 @@ +package community.flock.wirespec.integration.spring.kotlin.web + +import community.flock.wirespec.kotlin.Wirespec +import jakarta.servlet.http.HttpServletRequest +import java.util.stream.Collectors +import org.springframework.core.MethodParameter +import org.springframework.web.bind.support.WebDataBinderFactory +import org.springframework.web.context.request.NativeWebRequest +import org.springframework.web.method.support.HandlerMethodArgumentResolver +import org.springframework.web.method.support.ModelAndViewContainer +import kotlin.reflect.full.companionObjectInstance + +class WirespecMethodArgumentResolver( + private val wirespecSerialization: Wirespec.Serialization +) : HandlerMethodArgumentResolver { + + override fun supportsParameter(parameter: MethodParameter): Boolean = + Wirespec.Request::class.java.isAssignableFrom(parameter.parameterType) + + override fun resolveArgument( + parameter: MethodParameter, + mavContainer: ModelAndViewContainer?, + webRequest: NativeWebRequest, + binderFactory: WebDataBinderFactory? + ): Wirespec.Request<*> { + val servletRequest = webRequest.nativeRequest as HttpServletRequest + val declaringClass = parameter.parameterType.declaringClass + val handler = declaringClass.declaredClasses.toList() + .find { it.simpleName == "Handler" } + ?: error("Handler not found") + val instance = handler.kotlin.companionObjectInstance as Wirespec.Server<*, *> + val server = instance.server(wirespecSerialization) + return server.from(servletRequest.toRawRequest()) + } +} + +fun HttpServletRequest.toRawRequest(): Wirespec.RawRequest = Wirespec.RawRequest( + method = method, + path = pathInfo?.split("/")?.drop(1) ?: emptyList(), + queries = queryString + ?.split("&") + ?.associate { + val (key, value) = it.split("=") + key to value + } + .orEmpty(), + headers = headerNames.toList().associateWith(::getHeader), + body = reader.lines().collect(Collectors.joining(System.lineSeparator())) +) diff --git a/src/integration/spring/src/jvmMain/kotlin/community/flock/wirespec/integration/spring/kotlin/web/WirespecResponseBodyAdvice.kt b/src/integration/spring/src/jvmMain/kotlin/community/flock/wirespec/integration/spring/kotlin/web/WirespecResponseBodyAdvice.kt new file mode 100644 index 00000000..518f640a --- /dev/null +++ b/src/integration/spring/src/jvmMain/kotlin/community/flock/wirespec/integration/spring/kotlin/web/WirespecResponseBodyAdvice.kt @@ -0,0 +1,49 @@ +package community.flock.wirespec.integration.spring.kotlin.web + +import com.fasterxml.jackson.databind.ObjectMapper +import community.flock.wirespec.kotlin.Wirespec +import org.springframework.core.MethodParameter +import org.springframework.http.HttpStatusCode +import org.springframework.http.MediaType +import org.springframework.http.converter.HttpMessageConverter +import org.springframework.http.server.ServerHttpRequest +import org.springframework.http.server.ServerHttpResponse +import org.springframework.web.bind.annotation.ControllerAdvice +import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice +import kotlin.reflect.full.companionObjectInstance + +@ControllerAdvice +class WirespecResponseBodyAdvice( + private val objectMapper: ObjectMapper, + private val wirespecSerialization: Wirespec.Serialization +) : ResponseBodyAdvice { + + override fun supports(returnType: MethodParameter, converterType: Class?>): Boolean = + Wirespec.Response::class.java.isAssignableFrom(returnType.parameterType) + + override fun beforeBodyWrite( + body: Any?, + returnType: MethodParameter, + selectedContentType: MediaType, + selectedConverterType: Class>, + request: ServerHttpRequest, + response: ServerHttpResponse + ): Any? { + val declaringClass = returnType.parameterType.declaringClass + val handler = declaringClass.declaredClasses.toList() + .find { it.simpleName == "Handler" } + ?: error("Handler not found") + val instance = handler + .kotlin.companionObjectInstance as Wirespec.Server, Wirespec.Response<*>> + val server = instance.server(wirespecSerialization) + return when (body) { + is Wirespec.Response<*> -> { + val rawResponse = server.to(body) + response.setStatusCode(HttpStatusCode.valueOf(rawResponse.statusCode)) + rawResponse.body?.let { objectMapper.readTree(it) } + } + + else -> body + } + } +} diff --git a/src/integration/spring/src/jvmTest/kotlin/community/flock/wirespec/integration/spring/kotlin/GenerateTestClasses.kt b/src/integration/spring/src/jvmTest/kotlin/community/flock/wirespec/integration/spring/kotlin/GenerateTestClasses.kt new file mode 100644 index 00000000..1e2bf3a7 --- /dev/null +++ b/src/integration/spring/src/jvmTest/kotlin/community/flock/wirespec/integration/spring/kotlin/GenerateTestClasses.kt @@ -0,0 +1,32 @@ +package community.flock.wirespec.integration.spring.kotlin + +import community.flock.wirespec.integration.spring.kotlin.emit.SpringKotlinEmitter +import community.flock.wirespec.openapi.v3.OpenApiV3Parser +import java.io.File +import kotlin.test.Test + +class GenerateTestClasses { + + private val basePkg = "community.flock.wirespec.integration.spring.kotlin" + private val kotlinPkg = "${basePkg}.generated" + + private val kotlinEmitter = SpringKotlinEmitter(kotlinPkg) + + private fun pkgToPath(pkg: String) = pkg.split(".").joinToString("/") + + private val baseDir = File("src/jvmTest") + private val outputDir = baseDir.resolve("kotlin").resolve(pkgToPath(kotlinPkg)) + + @Test + fun generate() { + val petstoreFile = File("src/jvmTest/resources/petstore.json").readText() + val ast = OpenApiV3Parser.parse(petstoreFile) + val emittedKotlin = kotlinEmitter.emit(ast) + + outputDir.mkdirs() + emittedKotlin.forEach { + outputDir.resolve("Petstorev3.kt").writeText(it.result) + } + } +} + diff --git a/src/integration/spring/src/jvmTest/kotlin/community/flock/wirespec/integration/spring/kotlin/application/Application.kt b/src/integration/spring/src/jvmTest/kotlin/community/flock/wirespec/integration/spring/kotlin/application/Application.kt new file mode 100644 index 00000000..e573b0c6 --- /dev/null +++ b/src/integration/spring/src/jvmTest/kotlin/community/flock/wirespec/integration/spring/kotlin/application/Application.kt @@ -0,0 +1,14 @@ +package community.flock.wirespec.integration.spring.kotlin.application + +import community.flock.wirespec.integration.spring.kotlin.configuration.WirespecConfiguration +import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.boot.runApplication +import org.springframework.context.annotation.Import + +@SpringBootApplication +@Import(WirespecConfiguration::class) +open class Application + +fun main(args: Array) { + runApplication(*args) +} \ No newline at end of file diff --git a/src/integration/spring/src/jvmTest/kotlin/community/flock/wirespec/integration/spring/kotlin/application/Controller.kt b/src/integration/spring/src/jvmTest/kotlin/community/flock/wirespec/integration/spring/kotlin/application/Controller.kt new file mode 100644 index 00000000..6eaddc41 --- /dev/null +++ b/src/integration/spring/src/jvmTest/kotlin/community/flock/wirespec/integration/spring/kotlin/application/Controller.kt @@ -0,0 +1,37 @@ +package community.flock.wirespec.integration.spring.kotlin.application + +import community.flock.wirespec.integration.spring.kotlin.generated.AddPetEndpoint +import community.flock.wirespec.integration.spring.kotlin.generated.DeletePetEndpoint +import community.flock.wirespec.integration.spring.kotlin.generated.GetPetByIdEndpoint +import community.flock.wirespec.integration.spring.kotlin.generated.UpdatePetEndpoint +import org.springframework.web.bind.annotation.RestController + +@RestController +class Controller( + private val service: Service +) : AddPetEndpoint.Handler, GetPetByIdEndpoint.Handler, UpdatePetEndpoint.Handler, DeletePetEndpoint.Handler { + + override suspend fun addPet(request: AddPetEndpoint.Request): AddPetEndpoint.Response<*> { + service.create(request.body) + return AddPetEndpoint.Response200(request.body) + } + + override suspend fun getPetById(request: GetPetByIdEndpoint.Request): GetPetByIdEndpoint.Response<*> { + return service.list.find { it.id == request.path.petId } + ?.let { GetPetByIdEndpoint.Response200(it) } + ?: GetPetByIdEndpoint.Response404(Unit) + } + + override suspend fun updatePet(request: UpdatePetEndpoint.Request): UpdatePetEndpoint.Response<*> { + service.update(request.body) + return UpdatePetEndpoint.Response200(request.body) + } + + override suspend fun deletePet(request: DeletePetEndpoint.Request): DeletePetEndpoint.Response<*> { + val id = 1L + return service.delete(id).let { + DeletePetEndpoint.Response400(Unit) + } + } + +} \ No newline at end of file diff --git a/src/integration/spring/src/jvmTest/kotlin/community/flock/wirespec/integration/spring/kotlin/application/Service.kt b/src/integration/spring/src/jvmTest/kotlin/community/flock/wirespec/integration/spring/kotlin/application/Service.kt new file mode 100644 index 00000000..b8ec38a0 --- /dev/null +++ b/src/integration/spring/src/jvmTest/kotlin/community/flock/wirespec/integration/spring/kotlin/application/Service.kt @@ -0,0 +1,27 @@ +package community.flock.wirespec.integration.spring.kotlin.application + +import community.flock.wirespec.integration.spring.kotlin.generated.Pet +import org.springframework.stereotype.Service + + +@Service +class Service { + + val list = mutableListOf() + + fun create(pet: Pet): Pet = pet + .also { list.add(it) } + + fun read(id: Long): Pet? = list.find { it.id == id } + + fun update(pet: Pet): Pet { + list.removeIf { it.id == pet.id } + list.add(pet) + return pet + } + + fun delete(id: Long) { + list.removeIf { it.id == id } + } + +} diff --git a/src/integration/spring/src/jvmTest/kotlin/community/flock/wirespec/integration/spring/kotlin/generated/Petstorev3.kt b/src/integration/spring/src/jvmTest/kotlin/community/flock/wirespec/integration/spring/kotlin/generated/Petstorev3.kt new file mode 100644 index 00000000..e298bc19 --- /dev/null +++ b/src/integration/spring/src/jvmTest/kotlin/community/flock/wirespec/integration/spring/kotlin/generated/Petstorev3.kt @@ -0,0 +1,1753 @@ +package community.flock.wirespec.integration.spring.kotlin.generated + +import community.flock.wirespec.kotlin.Wirespec +import kotlin.reflect.typeOf + +object AddPetEndpoint : Wirespec.Endpoint { + data object Path : Wirespec.Path + + data object Queries : Wirespec.Queries + + data object Headers : Wirespec.Request.Headers + + class Request( + override val body: Pet, + ) : Wirespec.Request { + override val path = Path + override val method = Wirespec.Method.POST + override val queries = Queries + override val headers = Headers + } + + fun toRequest(serialization: Wirespec.Serializer, request: Request): Wirespec.RawRequest = + Wirespec.RawRequest( + path = listOf("pet"), + method = request.method.name, + queries = emptyMap(), + headers = emptyMap(), + body = serialization.serialize(request.body, typeOf()), + ) + + fun fromRequest(serialization: Wirespec.Deserializer, request: Wirespec.RawRequest): Request = + Request( + body = serialization.deserialize(requireNotNull(request.body) { "body is null" }, typeOf()), + ) + + sealed interface Response : Wirespec.Response + sealed interface Response2XX : Response + sealed interface Response4XX : Response + + data class Response200(override val body: Pet) : Response2XX { + override val status = 200 + override val headers = Headers + data object Headers : Wirespec.Response.Headers + } + data class Response405(override val body: Unit) : Response4XX { + override val status = 405 + override val headers = Headers + data object Headers : Wirespec.Response.Headers + } + + fun toResponse(serialization: Wirespec.Serializer, response: Response<*>): Wirespec.RawResponse = + when(response) { + is Response200 -> Wirespec.RawResponse( + statusCode = response.status, + headers = mapOf(), + body = serialization.serialize(response.body, typeOf()), + ) + is Response405 -> Wirespec.RawResponse( + statusCode = response.status, + headers = mapOf(), + body = null, + ) + } + + fun fromResponse(serialization: Wirespec.Deserializer, response: Wirespec.RawResponse): Response<*> = + when (response.statusCode) { + 200 -> Response200( + body = serialization.deserialize(requireNotNull(response.body) { "body is null" }, typeOf()), + ) + 405 -> Response405( + body = serialization.deserialize(requireNotNull(response.body) { "body is null" }, typeOf()), + ) + else -> error("Cannot match response with status: ${response.statusCode}") + } + + interface Handler: Wirespec.Handler { + @org.springframework.web.bind.annotation.PostMapping("/pet") + +suspend fun addPet(request: Request): Response<*> + companion object: Wirespec.Server>, Wirespec.Client> { + override val pathTemplate = "/pet" + override val method = "POST" + override fun server(serialization: Wirespec.Serialization) = object : Wirespec.ServerEdge> { + override fun from(request: Wirespec.RawRequest) = fromRequest(serialization, request) + override fun to(response: Response<*>) = toResponse(serialization, response) + } + override fun client(serialization: Wirespec.Serialization) = object : Wirespec.ClientEdge> { + override fun to(request: Request) = toRequest(serialization, request) + override fun from(response: Wirespec.RawResponse) = fromResponse(serialization, response) + } + } + } +} +object UpdatePetEndpoint : Wirespec.Endpoint { + data object Path : Wirespec.Path + + data object Queries : Wirespec.Queries + + data object Headers : Wirespec.Request.Headers + + class Request( + override val body: Pet, + ) : Wirespec.Request { + override val path = Path + override val method = Wirespec.Method.PUT + override val queries = Queries + override val headers = Headers + } + + fun toRequest(serialization: Wirespec.Serializer, request: Request): Wirespec.RawRequest = + Wirespec.RawRequest( + path = listOf("pet"), + method = request.method.name, + queries = emptyMap(), + headers = emptyMap(), + body = serialization.serialize(request.body, typeOf()), + ) + + fun fromRequest(serialization: Wirespec.Deserializer, request: Wirespec.RawRequest): Request = + Request( + body = serialization.deserialize(requireNotNull(request.body) { "body is null" }, typeOf()), + ) + + sealed interface Response : Wirespec.Response + sealed interface Response2XX : Response + sealed interface Response4XX : Response + + data class Response200(override val body: Pet) : Response2XX { + override val status = 200 + override val headers = Headers + data object Headers : Wirespec.Response.Headers + } + data class Response400(override val body: Unit) : Response4XX { + override val status = 400 + override val headers = Headers + data object Headers : Wirespec.Response.Headers + } + data class Response404(override val body: Unit) : Response4XX { + override val status = 404 + override val headers = Headers + data object Headers : Wirespec.Response.Headers + } + data class Response405(override val body: Unit) : Response4XX { + override val status = 405 + override val headers = Headers + data object Headers : Wirespec.Response.Headers + } + + fun toResponse(serialization: Wirespec.Serializer, response: Response<*>): Wirespec.RawResponse = + when(response) { + is Response200 -> Wirespec.RawResponse( + statusCode = response.status, + headers = mapOf(), + body = serialization.serialize(response.body, typeOf()), + ) + is Response400 -> Wirespec.RawResponse( + statusCode = response.status, + headers = mapOf(), + body = null, + ) + is Response404 -> Wirespec.RawResponse( + statusCode = response.status, + headers = mapOf(), + body = null, + ) + is Response405 -> Wirespec.RawResponse( + statusCode = response.status, + headers = mapOf(), + body = null, + ) + } + + fun fromResponse(serialization: Wirespec.Deserializer, response: Wirespec.RawResponse): Response<*> = + when (response.statusCode) { + 200 -> Response200( + body = serialization.deserialize(requireNotNull(response.body) { "body is null" }, typeOf()), + ) + 400 -> Response400( + body = serialization.deserialize(requireNotNull(response.body) { "body is null" }, typeOf()), + ) + 404 -> Response404( + body = serialization.deserialize(requireNotNull(response.body) { "body is null" }, typeOf()), + ) + 405 -> Response405( + body = serialization.deserialize(requireNotNull(response.body) { "body is null" }, typeOf()), + ) + else -> error("Cannot match response with status: ${response.statusCode}") + } + + interface Handler: Wirespec.Handler { + @org.springframework.web.bind.annotation.PutMapping("/pet") + +suspend fun updatePet(request: Request): Response<*> + companion object: Wirespec.Server>, Wirespec.Client> { + override val pathTemplate = "/pet" + override val method = "PUT" + override fun server(serialization: Wirespec.Serialization) = object : Wirespec.ServerEdge> { + override fun from(request: Wirespec.RawRequest) = fromRequest(serialization, request) + override fun to(response: Response<*>) = toResponse(serialization, response) + } + override fun client(serialization: Wirespec.Serialization) = object : Wirespec.ClientEdge> { + override fun to(request: Request) = toRequest(serialization, request) + override fun from(response: Wirespec.RawResponse) = fromResponse(serialization, response) + } + } + } +} +object FindPetsByStatusEndpoint : Wirespec.Endpoint { + data object Path : Wirespec.Path + + data class Queries( + val status: FindPetsByStatusParameterStatus?, + ) : Wirespec.Queries + + data object Headers : Wirespec.Request.Headers + + class Request( + status: FindPetsByStatusParameterStatus? + ) : Wirespec.Request { + override val path = Path + override val method = Wirespec.Method.GET + override val queries = Queries(status) + override val headers = Headers + override val body = Unit + } + + fun toRequest(serialization: Wirespec.Serializer, request: Request): Wirespec.RawRequest = + Wirespec.RawRequest( + path = listOf("pet", "findByStatus"), + method = request.method.name, + queries = listOf(request.queries.status?.let{"status" to serialization.serialize(it, typeOf())}).filterNotNull().toMap(), + headers = emptyMap(), + body = serialization.serialize(request.body, typeOf()), + ) + + fun fromRequest(serialization: Wirespec.Deserializer, request: Wirespec.RawRequest): Request = + Request( + status = request.queries["status"]?.let{ serialization.deserialize(it, typeOf()) } + ) + + sealed interface Response : Wirespec.Response + sealed interface Response2XX : Response + sealed interface Response4XX : Response + + data class Response200(override val body: List) : Response2XX> { + override val status = 200 + override val headers = Headers + data object Headers : Wirespec.Response.Headers + } + data class Response400(override val body: Unit) : Response4XX { + override val status = 400 + override val headers = Headers + data object Headers : Wirespec.Response.Headers + } + + fun toResponse(serialization: Wirespec.Serializer, response: Response<*>): Wirespec.RawResponse = + when(response) { + is Response200 -> Wirespec.RawResponse( + statusCode = response.status, + headers = mapOf(), + body = serialization.serialize(response.body, typeOf>()), + ) + is Response400 -> Wirespec.RawResponse( + statusCode = response.status, + headers = mapOf(), + body = null, + ) + } + + fun fromResponse(serialization: Wirespec.Deserializer, response: Wirespec.RawResponse): Response<*> = + when (response.statusCode) { + 200 -> Response200( + body = serialization.deserialize(requireNotNull(response.body) { "body is null" }, typeOf>()), + ) + 400 -> Response400( + body = serialization.deserialize(requireNotNull(response.body) { "body is null" }, typeOf()), + ) + else -> error("Cannot match response with status: ${response.statusCode}") + } + + interface Handler: Wirespec.Handler { + @org.springframework.web.bind.annotation.GetMapping("/pet/findByStatus") + +suspend fun findPetsByStatus(request: Request): Response<*> + companion object: Wirespec.Server>, Wirespec.Client> { + override val pathTemplate = "/pet/findByStatus" + override val method = "GET" + override fun server(serialization: Wirespec.Serialization) = object : Wirespec.ServerEdge> { + override fun from(request: Wirespec.RawRequest) = fromRequest(serialization, request) + override fun to(response: Response<*>) = toResponse(serialization, response) + } + override fun client(serialization: Wirespec.Serialization) = object : Wirespec.ClientEdge> { + override fun to(request: Request) = toRequest(serialization, request) + override fun from(response: Wirespec.RawResponse) = fromResponse(serialization, response) + } + } + } +} +object FindPetsByTagsEndpoint : Wirespec.Endpoint { + data object Path : Wirespec.Path + + data class Queries( + val tags: List?, + ) : Wirespec.Queries + + data object Headers : Wirespec.Request.Headers + + class Request( + tags: List? + ) : Wirespec.Request { + override val path = Path + override val method = Wirespec.Method.GET + override val queries = Queries(tags) + override val headers = Headers + override val body = Unit + } + + fun toRequest(serialization: Wirespec.Serializer, request: Request): Wirespec.RawRequest = + Wirespec.RawRequest( + path = listOf("pet", "findByTags"), + method = request.method.name, + queries = listOf(request.queries.tags?.let{"tags" to serialization.serialize(it, typeOf>())}).filterNotNull().toMap(), + headers = emptyMap(), + body = serialization.serialize(request.body, typeOf()), + ) + + fun fromRequest(serialization: Wirespec.Deserializer, request: Wirespec.RawRequest): Request = + Request( + tags = request.queries["tags"]?.let{ serialization.deserialize(it, typeOf>()) } + ) + + sealed interface Response : Wirespec.Response + sealed interface Response2XX : Response + sealed interface Response4XX : Response + + data class Response200(override val body: List) : Response2XX> { + override val status = 200 + override val headers = Headers + data object Headers : Wirespec.Response.Headers + } + data class Response400(override val body: Unit) : Response4XX { + override val status = 400 + override val headers = Headers + data object Headers : Wirespec.Response.Headers + } + + fun toResponse(serialization: Wirespec.Serializer, response: Response<*>): Wirespec.RawResponse = + when(response) { + is Response200 -> Wirespec.RawResponse( + statusCode = response.status, + headers = mapOf(), + body = serialization.serialize(response.body, typeOf>()), + ) + is Response400 -> Wirespec.RawResponse( + statusCode = response.status, + headers = mapOf(), + body = null, + ) + } + + fun fromResponse(serialization: Wirespec.Deserializer, response: Wirespec.RawResponse): Response<*> = + when (response.statusCode) { + 200 -> Response200( + body = serialization.deserialize(requireNotNull(response.body) { "body is null" }, typeOf>()), + ) + 400 -> Response400( + body = serialization.deserialize(requireNotNull(response.body) { "body is null" }, typeOf()), + ) + else -> error("Cannot match response with status: ${response.statusCode}") + } + + interface Handler: Wirespec.Handler { + @org.springframework.web.bind.annotation.GetMapping("/pet/findByTags") + +suspend fun findPetsByTags(request: Request): Response<*> + companion object: Wirespec.Server>, Wirespec.Client> { + override val pathTemplate = "/pet/findByTags" + override val method = "GET" + override fun server(serialization: Wirespec.Serialization) = object : Wirespec.ServerEdge> { + override fun from(request: Wirespec.RawRequest) = fromRequest(serialization, request) + override fun to(response: Response<*>) = toResponse(serialization, response) + } + override fun client(serialization: Wirespec.Serialization) = object : Wirespec.ClientEdge> { + override fun to(request: Request) = toRequest(serialization, request) + override fun from(response: Wirespec.RawResponse) = fromResponse(serialization, response) + } + } + } +} +object GetPetByIdEndpoint : Wirespec.Endpoint { + data class Path( + val petId: Long, + ) : Wirespec.Path + + data object Queries : Wirespec.Queries + + data object Headers : Wirespec.Request.Headers + + class Request( + petId: Long + ) : Wirespec.Request { + override val path = Path(petId) + override val method = Wirespec.Method.GET + override val queries = Queries + override val headers = Headers + override val body = Unit + } + + fun toRequest(serialization: Wirespec.Serializer, request: Request): Wirespec.RawRequest = + Wirespec.RawRequest( + path = listOf("pet", request.path.petId.let{serialization.serialize(it, typeOf())}), + method = request.method.name, + queries = emptyMap(), + headers = emptyMap(), + body = serialization.serialize(request.body, typeOf()), + ) + + fun fromRequest(serialization: Wirespec.Deserializer, request: Wirespec.RawRequest): Request = + Request( + petId = serialization.deserialize(request.path[1], typeOf()) + ) + + sealed interface Response : Wirespec.Response + sealed interface Response2XX : Response + sealed interface Response4XX : Response + + data class Response200(override val body: Pet) : Response2XX { + override val status = 200 + override val headers = Headers + data object Headers : Wirespec.Response.Headers + } + data class Response400(override val body: Unit) : Response4XX { + override val status = 400 + override val headers = Headers + data object Headers : Wirespec.Response.Headers + } + data class Response404(override val body: Unit) : Response4XX { + override val status = 404 + override val headers = Headers + data object Headers : Wirespec.Response.Headers + } + + fun toResponse(serialization: Wirespec.Serializer, response: Response<*>): Wirespec.RawResponse = + when(response) { + is Response200 -> Wirespec.RawResponse( + statusCode = response.status, + headers = mapOf(), + body = serialization.serialize(response.body, typeOf()), + ) + is Response400 -> Wirespec.RawResponse( + statusCode = response.status, + headers = mapOf(), + body = null, + ) + is Response404 -> Wirespec.RawResponse( + statusCode = response.status, + headers = mapOf(), + body = null, + ) + } + + fun fromResponse(serialization: Wirespec.Deserializer, response: Wirespec.RawResponse): Response<*> = + when (response.statusCode) { + 200 -> Response200( + body = serialization.deserialize(requireNotNull(response.body) { "body is null" }, typeOf()), + ) + 400 -> Response400( + body = serialization.deserialize(requireNotNull(response.body) { "body is null" }, typeOf()), + ) + 404 -> Response404( + body = serialization.deserialize(requireNotNull(response.body) { "body is null" }, typeOf()), + ) + else -> error("Cannot match response with status: ${response.statusCode}") + } + + interface Handler: Wirespec.Handler { + @org.springframework.web.bind.annotation.GetMapping("/pet/{petId}") + +suspend fun getPetById(request: Request): Response<*> + companion object: Wirespec.Server>, Wirespec.Client> { + override val pathTemplate = "/pet/{petId}" + override val method = "GET" + override fun server(serialization: Wirespec.Serialization) = object : Wirespec.ServerEdge> { + override fun from(request: Wirespec.RawRequest) = fromRequest(serialization, request) + override fun to(response: Response<*>) = toResponse(serialization, response) + } + override fun client(serialization: Wirespec.Serialization) = object : Wirespec.ClientEdge> { + override fun to(request: Request) = toRequest(serialization, request) + override fun from(response: Wirespec.RawResponse) = fromResponse(serialization, response) + } + } + } +} +object UpdatePetWithFormEndpoint : Wirespec.Endpoint { + data class Path( + val petId: Long, + ) : Wirespec.Path + + data class Queries( + val name: String?, + val status: String?, + ) : Wirespec.Queries + + data object Headers : Wirespec.Request.Headers + + class Request( + petId: Long, + name: String?, status: String? + ) : Wirespec.Request { + override val path = Path(petId) + override val method = Wirespec.Method.POST + override val queries = Queries(name, status) + override val headers = Headers + override val body = Unit + } + + fun toRequest(serialization: Wirespec.Serializer, request: Request): Wirespec.RawRequest = + Wirespec.RawRequest( + path = listOf("pet", request.path.petId.let{serialization.serialize(it, typeOf())}), + method = request.method.name, + queries = listOf(request.queries.name?.let{"name" to serialization.serialize(it, typeOf())}, request.queries.status?.let{"status" to serialization.serialize(it, typeOf())}).filterNotNull().toMap(), + headers = emptyMap(), + body = serialization.serialize(request.body, typeOf()), + ) + + fun fromRequest(serialization: Wirespec.Deserializer, request: Wirespec.RawRequest): Request = + Request( + petId = serialization.deserialize(request.path[1], typeOf()), + name = request.queries["name"]?.let{ serialization.deserialize(it, typeOf()) }, status = request.queries["status"]?.let{ serialization.deserialize(it, typeOf()) } + ) + + sealed interface Response : Wirespec.Response + sealed interface Response4XX : Response + + data class Response405(override val body: Unit) : Response4XX { + override val status = 405 + override val headers = Headers + data object Headers : Wirespec.Response.Headers + } + + fun toResponse(serialization: Wirespec.Serializer, response: Response<*>): Wirespec.RawResponse = + when(response) { + is Response405 -> Wirespec.RawResponse( + statusCode = response.status, + headers = mapOf(), + body = null, + ) + } + + fun fromResponse(serialization: Wirespec.Deserializer, response: Wirespec.RawResponse): Response<*> = + when (response.statusCode) { + 405 -> Response405( + body = serialization.deserialize(requireNotNull(response.body) { "body is null" }, typeOf()), + ) + else -> error("Cannot match response with status: ${response.statusCode}") + } + + interface Handler: Wirespec.Handler { + @org.springframework.web.bind.annotation.PostMapping("/pet/{petId}") + +suspend fun updatePetWithForm(request: Request): Response<*> + companion object: Wirespec.Server>, Wirespec.Client> { + override val pathTemplate = "/pet/{petId}" + override val method = "POST" + override fun server(serialization: Wirespec.Serialization) = object : Wirespec.ServerEdge> { + override fun from(request: Wirespec.RawRequest) = fromRequest(serialization, request) + override fun to(response: Response<*>) = toResponse(serialization, response) + } + override fun client(serialization: Wirespec.Serialization) = object : Wirespec.ClientEdge> { + override fun to(request: Request) = toRequest(serialization, request) + override fun from(response: Wirespec.RawResponse) = fromResponse(serialization, response) + } + } + } +} +object DeletePetEndpoint : Wirespec.Endpoint { + data class Path( + val petId: Long, + ) : Wirespec.Path + + data object Queries : Wirespec.Queries + + data class Headers( + val api_key: String?, + ) : Wirespec.Request.Headers + + class Request( + petId: Long, + api_key: String? + ) : Wirespec.Request { + override val path = Path(petId) + override val method = Wirespec.Method.DELETE + override val queries = Queries + override val headers = Headers(api_key) + override val body = Unit + } + + fun toRequest(serialization: Wirespec.Serializer, request: Request): Wirespec.RawRequest = + Wirespec.RawRequest( + path = listOf("pet", request.path.petId.let{serialization.serialize(it, typeOf())}), + method = request.method.name, + queries = emptyMap(), + headers = listOf(request.headers.api_key?.let{"api_key" to serialization.serialize(it, typeOf())}).filterNotNull().toMap(), + body = serialization.serialize(request.body, typeOf()), + ) + + fun fromRequest(serialization: Wirespec.Deserializer, request: Wirespec.RawRequest): Request = + Request( + petId = serialization.deserialize(request.path[1], typeOf()), + api_key = request.headers["api_key"]?.let{ serialization.deserialize(it, typeOf()) } + ) + + sealed interface Response : Wirespec.Response + sealed interface Response4XX : Response + + data class Response400(override val body: Unit) : Response4XX { + override val status = 400 + override val headers = Headers + data object Headers : Wirespec.Response.Headers + } + + fun toResponse(serialization: Wirespec.Serializer, response: Response<*>): Wirespec.RawResponse = + when(response) { + is Response400 -> Wirespec.RawResponse( + statusCode = response.status, + headers = mapOf(), + body = null, + ) + } + + fun fromResponse(serialization: Wirespec.Deserializer, response: Wirespec.RawResponse): Response<*> = + when (response.statusCode) { + 400 -> Response400( + body = serialization.deserialize(requireNotNull(response.body) { "body is null" }, typeOf()), + ) + else -> error("Cannot match response with status: ${response.statusCode}") + } + + interface Handler: Wirespec.Handler { + @org.springframework.web.bind.annotation.DeleteMapping("/pet/{petId}") + +suspend fun deletePet(request: Request): Response<*> + companion object: Wirespec.Server>, Wirespec.Client> { + override val pathTemplate = "/pet/{petId}" + override val method = "DELETE" + override fun server(serialization: Wirespec.Serialization) = object : Wirespec.ServerEdge> { + override fun from(request: Wirespec.RawRequest) = fromRequest(serialization, request) + override fun to(response: Response<*>) = toResponse(serialization, response) + } + override fun client(serialization: Wirespec.Serialization) = object : Wirespec.ClientEdge> { + override fun to(request: Request) = toRequest(serialization, request) + override fun from(response: Wirespec.RawResponse) = fromResponse(serialization, response) + } + } + } +} +object UploadFileEndpoint : Wirespec.Endpoint { + data class Path( + val petId: Long, + ) : Wirespec.Path + + data class Queries( + val additionalMetadata: String?, + ) : Wirespec.Queries + + data object Headers : Wirespec.Request.Headers + + class Request( + petId: Long, + additionalMetadata: String?, + override val body: String, + ) : Wirespec.Request { + override val path = Path(petId) + override val method = Wirespec.Method.POST + override val queries = Queries(additionalMetadata) + override val headers = Headers + } + + fun toRequest(serialization: Wirespec.Serializer, request: Request): Wirespec.RawRequest = + Wirespec.RawRequest( + path = listOf("pet", request.path.petId.let{serialization.serialize(it, typeOf())}, "uploadImage"), + method = request.method.name, + queries = listOf(request.queries.additionalMetadata?.let{"additionalMetadata" to serialization.serialize(it, typeOf())}).filterNotNull().toMap(), + headers = emptyMap(), + body = serialization.serialize(request.body, typeOf()), + ) + + fun fromRequest(serialization: Wirespec.Deserializer, request: Wirespec.RawRequest): Request = + Request( + petId = serialization.deserialize(request.path[1], typeOf()), + additionalMetadata = request.queries["additionalMetadata"]?.let{ serialization.deserialize(it, typeOf()) }, + body = serialization.deserialize(requireNotNull(request.body) { "body is null" }, typeOf()), + ) + + sealed interface Response : Wirespec.Response + sealed interface Response2XX : Response + + data class Response200(override val body: ApiResponse) : Response2XX { + override val status = 200 + override val headers = Headers + data object Headers : Wirespec.Response.Headers + } + + fun toResponse(serialization: Wirespec.Serializer, response: Response<*>): Wirespec.RawResponse = + when(response) { + is Response200 -> Wirespec.RawResponse( + statusCode = response.status, + headers = mapOf(), + body = serialization.serialize(response.body, typeOf()), + ) + } + + fun fromResponse(serialization: Wirespec.Deserializer, response: Wirespec.RawResponse): Response<*> = + when (response.statusCode) { + 200 -> Response200( + body = serialization.deserialize(requireNotNull(response.body) { "body is null" }, typeOf()), + ) + else -> error("Cannot match response with status: ${response.statusCode}") + } + + interface Handler: Wirespec.Handler { + @org.springframework.web.bind.annotation.PostMapping("/pet/{petId}/uploadImage") + +suspend fun uploadFile(request: Request): Response<*> + companion object: Wirespec.Server>, Wirespec.Client> { + override val pathTemplate = "/pet/{petId}/uploadImage" + override val method = "POST" + override fun server(serialization: Wirespec.Serialization) = object : Wirespec.ServerEdge> { + override fun from(request: Wirespec.RawRequest) = fromRequest(serialization, request) + override fun to(response: Response<*>) = toResponse(serialization, response) + } + override fun client(serialization: Wirespec.Serialization) = object : Wirespec.ClientEdge> { + override fun to(request: Request) = toRequest(serialization, request) + override fun from(response: Wirespec.RawResponse) = fromResponse(serialization, response) + } + } + } +} +object GetInventoryEndpoint : Wirespec.Endpoint { + data object Path : Wirespec.Path + + data object Queries : Wirespec.Queries + + data object Headers : Wirespec.Request.Headers + + object Request : Wirespec.Request { + override val path = Path + override val method = Wirespec.Method.GET + override val queries = Queries + override val headers = Headers + override val body = Unit + } + + fun toRequest(serialization: Wirespec.Serializer, request: Request): Wirespec.RawRequest = + Wirespec.RawRequest( + path = listOf("store", "inventory"), + method = request.method.name, + queries = emptyMap(), + headers = emptyMap(), + body = serialization.serialize(request.body, typeOf()), + ) + + fun fromRequest(serialization: Wirespec.Deserializer, request: Wirespec.RawRequest): Request = + Request + + sealed interface Response : Wirespec.Response + sealed interface Response2XX : Response + + data class Response200(override val body: Map) : Response2XX> { + override val status = 200 + override val headers = Headers + data object Headers : Wirespec.Response.Headers + } + + fun toResponse(serialization: Wirespec.Serializer, response: Response<*>): Wirespec.RawResponse = + when(response) { + is Response200 -> Wirespec.RawResponse( + statusCode = response.status, + headers = mapOf(), + body = serialization.serialize(response.body, typeOf>()), + ) + } + + fun fromResponse(serialization: Wirespec.Deserializer, response: Wirespec.RawResponse): Response<*> = + when (response.statusCode) { + 200 -> Response200( + body = serialization.deserialize(requireNotNull(response.body) { "body is null" }, typeOf>()), + ) + else -> error("Cannot match response with status: ${response.statusCode}") + } + + interface Handler: Wirespec.Handler { + @org.springframework.web.bind.annotation.GetMapping("/store/inventory") + +suspend fun getInventory(request: Request): Response<*> + companion object: Wirespec.Server>, Wirespec.Client> { + override val pathTemplate = "/store/inventory" + override val method = "GET" + override fun server(serialization: Wirespec.Serialization) = object : Wirespec.ServerEdge> { + override fun from(request: Wirespec.RawRequest) = fromRequest(serialization, request) + override fun to(response: Response<*>) = toResponse(serialization, response) + } + override fun client(serialization: Wirespec.Serialization) = object : Wirespec.ClientEdge> { + override fun to(request: Request) = toRequest(serialization, request) + override fun from(response: Wirespec.RawResponse) = fromResponse(serialization, response) + } + } + } +} +object PlaceOrderEndpoint : Wirespec.Endpoint { + data object Path : Wirespec.Path + + data object Queries : Wirespec.Queries + + data object Headers : Wirespec.Request.Headers + + class Request( + override val body: Order, + ) : Wirespec.Request { + override val path = Path + override val method = Wirespec.Method.POST + override val queries = Queries + override val headers = Headers + } + + fun toRequest(serialization: Wirespec.Serializer, request: Request): Wirespec.RawRequest = + Wirespec.RawRequest( + path = listOf("store", "order"), + method = request.method.name, + queries = emptyMap(), + headers = emptyMap(), + body = serialization.serialize(request.body, typeOf()), + ) + + fun fromRequest(serialization: Wirespec.Deserializer, request: Wirespec.RawRequest): Request = + Request( + body = serialization.deserialize(requireNotNull(request.body) { "body is null" }, typeOf()), + ) + + sealed interface Response : Wirespec.Response + sealed interface Response2XX : Response + sealed interface Response4XX : Response + + data class Response200(override val body: Order) : Response2XX { + override val status = 200 + override val headers = Headers + data object Headers : Wirespec.Response.Headers + } + data class Response405(override val body: Unit) : Response4XX { + override val status = 405 + override val headers = Headers + data object Headers : Wirespec.Response.Headers + } + + fun toResponse(serialization: Wirespec.Serializer, response: Response<*>): Wirespec.RawResponse = + when(response) { + is Response200 -> Wirespec.RawResponse( + statusCode = response.status, + headers = mapOf(), + body = serialization.serialize(response.body, typeOf()), + ) + is Response405 -> Wirespec.RawResponse( + statusCode = response.status, + headers = mapOf(), + body = null, + ) + } + + fun fromResponse(serialization: Wirespec.Deserializer, response: Wirespec.RawResponse): Response<*> = + when (response.statusCode) { + 200 -> Response200( + body = serialization.deserialize(requireNotNull(response.body) { "body is null" }, typeOf()), + ) + 405 -> Response405( + body = serialization.deserialize(requireNotNull(response.body) { "body is null" }, typeOf()), + ) + else -> error("Cannot match response with status: ${response.statusCode}") + } + + interface Handler: Wirespec.Handler { + @org.springframework.web.bind.annotation.PostMapping("/store/order") + +suspend fun placeOrder(request: Request): Response<*> + companion object: Wirespec.Server>, Wirespec.Client> { + override val pathTemplate = "/store/order" + override val method = "POST" + override fun server(serialization: Wirespec.Serialization) = object : Wirespec.ServerEdge> { + override fun from(request: Wirespec.RawRequest) = fromRequest(serialization, request) + override fun to(response: Response<*>) = toResponse(serialization, response) + } + override fun client(serialization: Wirespec.Serialization) = object : Wirespec.ClientEdge> { + override fun to(request: Request) = toRequest(serialization, request) + override fun from(response: Wirespec.RawResponse) = fromResponse(serialization, response) + } + } + } +} +object GetOrderByIdEndpoint : Wirespec.Endpoint { + data class Path( + val orderId: Long, + ) : Wirespec.Path + + data object Queries : Wirespec.Queries + + data object Headers : Wirespec.Request.Headers + + class Request( + orderId: Long + ) : Wirespec.Request { + override val path = Path(orderId) + override val method = Wirespec.Method.GET + override val queries = Queries + override val headers = Headers + override val body = Unit + } + + fun toRequest(serialization: Wirespec.Serializer, request: Request): Wirespec.RawRequest = + Wirespec.RawRequest( + path = listOf("store", "order", request.path.orderId.let{serialization.serialize(it, typeOf())}), + method = request.method.name, + queries = emptyMap(), + headers = emptyMap(), + body = serialization.serialize(request.body, typeOf()), + ) + + fun fromRequest(serialization: Wirespec.Deserializer, request: Wirespec.RawRequest): Request = + Request( + orderId = serialization.deserialize(request.path[2], typeOf()) + ) + + sealed interface Response : Wirespec.Response + sealed interface Response2XX : Response + sealed interface Response4XX : Response + + data class Response200(override val body: Order) : Response2XX { + override val status = 200 + override val headers = Headers + data object Headers : Wirespec.Response.Headers + } + data class Response400(override val body: Unit) : Response4XX { + override val status = 400 + override val headers = Headers + data object Headers : Wirespec.Response.Headers + } + data class Response404(override val body: Unit) : Response4XX { + override val status = 404 + override val headers = Headers + data object Headers : Wirespec.Response.Headers + } + + fun toResponse(serialization: Wirespec.Serializer, response: Response<*>): Wirespec.RawResponse = + when(response) { + is Response200 -> Wirespec.RawResponse( + statusCode = response.status, + headers = mapOf(), + body = serialization.serialize(response.body, typeOf()), + ) + is Response400 -> Wirespec.RawResponse( + statusCode = response.status, + headers = mapOf(), + body = null, + ) + is Response404 -> Wirespec.RawResponse( + statusCode = response.status, + headers = mapOf(), + body = null, + ) + } + + fun fromResponse(serialization: Wirespec.Deserializer, response: Wirespec.RawResponse): Response<*> = + when (response.statusCode) { + 200 -> Response200( + body = serialization.deserialize(requireNotNull(response.body) { "body is null" }, typeOf()), + ) + 400 -> Response400( + body = serialization.deserialize(requireNotNull(response.body) { "body is null" }, typeOf()), + ) + 404 -> Response404( + body = serialization.deserialize(requireNotNull(response.body) { "body is null" }, typeOf()), + ) + else -> error("Cannot match response with status: ${response.statusCode}") + } + + interface Handler: Wirespec.Handler { + @org.springframework.web.bind.annotation.GetMapping("/store/order/{orderId}") + +suspend fun getOrderById(request: Request): Response<*> + companion object: Wirespec.Server>, Wirespec.Client> { + override val pathTemplate = "/store/order/{orderId}" + override val method = "GET" + override fun server(serialization: Wirespec.Serialization) = object : Wirespec.ServerEdge> { + override fun from(request: Wirespec.RawRequest) = fromRequest(serialization, request) + override fun to(response: Response<*>) = toResponse(serialization, response) + } + override fun client(serialization: Wirespec.Serialization) = object : Wirespec.ClientEdge> { + override fun to(request: Request) = toRequest(serialization, request) + override fun from(response: Wirespec.RawResponse) = fromResponse(serialization, response) + } + } + } +} +object DeleteOrderEndpoint : Wirespec.Endpoint { + data class Path( + val orderId: Long, + ) : Wirespec.Path + + data object Queries : Wirespec.Queries + + data object Headers : Wirespec.Request.Headers + + class Request( + orderId: Long + ) : Wirespec.Request { + override val path = Path(orderId) + override val method = Wirespec.Method.DELETE + override val queries = Queries + override val headers = Headers + override val body = Unit + } + + fun toRequest(serialization: Wirespec.Serializer, request: Request): Wirespec.RawRequest = + Wirespec.RawRequest( + path = listOf("store", "order", request.path.orderId.let{serialization.serialize(it, typeOf())}), + method = request.method.name, + queries = emptyMap(), + headers = emptyMap(), + body = serialization.serialize(request.body, typeOf()), + ) + + fun fromRequest(serialization: Wirespec.Deserializer, request: Wirespec.RawRequest): Request = + Request( + orderId = serialization.deserialize(request.path[2], typeOf()) + ) + + sealed interface Response : Wirespec.Response + sealed interface Response4XX : Response + + data class Response400(override val body: Unit) : Response4XX { + override val status = 400 + override val headers = Headers + data object Headers : Wirespec.Response.Headers + } + data class Response404(override val body: Unit) : Response4XX { + override val status = 404 + override val headers = Headers + data object Headers : Wirespec.Response.Headers + } + + fun toResponse(serialization: Wirespec.Serializer, response: Response<*>): Wirespec.RawResponse = + when(response) { + is Response400 -> Wirespec.RawResponse( + statusCode = response.status, + headers = mapOf(), + body = null, + ) + is Response404 -> Wirespec.RawResponse( + statusCode = response.status, + headers = mapOf(), + body = null, + ) + } + + fun fromResponse(serialization: Wirespec.Deserializer, response: Wirespec.RawResponse): Response<*> = + when (response.statusCode) { + 400 -> Response400( + body = serialization.deserialize(requireNotNull(response.body) { "body is null" }, typeOf()), + ) + 404 -> Response404( + body = serialization.deserialize(requireNotNull(response.body) { "body is null" }, typeOf()), + ) + else -> error("Cannot match response with status: ${response.statusCode}") + } + + interface Handler: Wirespec.Handler { + @org.springframework.web.bind.annotation.DeleteMapping("/store/order/{orderId}") + +suspend fun deleteOrder(request: Request): Response<*> + companion object: Wirespec.Server>, Wirespec.Client> { + override val pathTemplate = "/store/order/{orderId}" + override val method = "DELETE" + override fun server(serialization: Wirespec.Serialization) = object : Wirespec.ServerEdge> { + override fun from(request: Wirespec.RawRequest) = fromRequest(serialization, request) + override fun to(response: Response<*>) = toResponse(serialization, response) + } + override fun client(serialization: Wirespec.Serialization) = object : Wirespec.ClientEdge> { + override fun to(request: Request) = toRequest(serialization, request) + override fun from(response: Wirespec.RawResponse) = fromResponse(serialization, response) + } + } + } +} +object CreateUserEndpoint : Wirespec.Endpoint { + data object Path : Wirespec.Path + + data object Queries : Wirespec.Queries + + data object Headers : Wirespec.Request.Headers + + class Request( + override val body: User, + ) : Wirespec.Request { + override val path = Path + override val method = Wirespec.Method.POST + override val queries = Queries + override val headers = Headers + } + + fun toRequest(serialization: Wirespec.Serializer, request: Request): Wirespec.RawRequest = + Wirespec.RawRequest( + path = listOf("user"), + method = request.method.name, + queries = emptyMap(), + headers = emptyMap(), + body = serialization.serialize(request.body, typeOf()), + ) + + fun fromRequest(serialization: Wirespec.Deserializer, request: Wirespec.RawRequest): Request = + Request( + body = serialization.deserialize(requireNotNull(request.body) { "body is null" }, typeOf()), + ) + + sealed interface Response : Wirespec.Response + sealed interface ResponsedXX : Response + + data class Responsedefault(override val body: User) : ResponsedXX { + override val status = 200 + override val headers = Headers + data object Headers : Wirespec.Response.Headers + } + + fun toResponse(serialization: Wirespec.Serializer, response: Response<*>): Wirespec.RawResponse = + when(response) { + is Responsedefault -> Wirespec.RawResponse( + statusCode = response.status, + headers = mapOf(), + body = serialization.serialize(response.body, typeOf()), + ) + } + + fun fromResponse(serialization: Wirespec.Deserializer, response: Wirespec.RawResponse): Response<*> = + when (response.statusCode) { + + else -> error("Cannot match response with status: ${response.statusCode}") + } + + interface Handler: Wirespec.Handler { + @org.springframework.web.bind.annotation.PostMapping("/user") + +suspend fun createUser(request: Request): Response<*> + companion object: Wirespec.Server>, Wirespec.Client> { + override val pathTemplate = "/user" + override val method = "POST" + override fun server(serialization: Wirespec.Serialization) = object : Wirespec.ServerEdge> { + override fun from(request: Wirespec.RawRequest) = fromRequest(serialization, request) + override fun to(response: Response<*>) = toResponse(serialization, response) + } + override fun client(serialization: Wirespec.Serialization) = object : Wirespec.ClientEdge> { + override fun to(request: Request) = toRequest(serialization, request) + override fun from(response: Wirespec.RawResponse) = fromResponse(serialization, response) + } + } + } +} +object CreateUsersWithListInputEndpoint : Wirespec.Endpoint { + data object Path : Wirespec.Path + + data object Queries : Wirespec.Queries + + data object Headers : Wirespec.Request.Headers + + class Request( + override val body: List, + ) : Wirespec.Request> { + override val path = Path + override val method = Wirespec.Method.POST + override val queries = Queries + override val headers = Headers + } + + fun toRequest(serialization: Wirespec.Serializer, request: Request): Wirespec.RawRequest = + Wirespec.RawRequest( + path = listOf("user", "createWithList"), + method = request.method.name, + queries = emptyMap(), + headers = emptyMap(), + body = serialization.serialize(request.body, typeOf>()), + ) + + fun fromRequest(serialization: Wirespec.Deserializer, request: Wirespec.RawRequest): Request = + Request( + body = serialization.deserialize(requireNotNull(request.body) { "body is null" }, typeOf>()), + ) + + sealed interface Response : Wirespec.Response + sealed interface Response2XX : Response + sealed interface ResponsedXX : Response + + data class Response200(override val body: User) : Response2XX { + override val status = 200 + override val headers = Headers + data object Headers : Wirespec.Response.Headers + } + data class Responsedefault(override val body: Unit) : ResponsedXX { + override val status = 200 + override val headers = Headers + data object Headers : Wirespec.Response.Headers + } + + fun toResponse(serialization: Wirespec.Serializer, response: Response<*>): Wirespec.RawResponse = + when(response) { + is Response200 -> Wirespec.RawResponse( + statusCode = response.status, + headers = mapOf(), + body = serialization.serialize(response.body, typeOf()), + ) + is Responsedefault -> Wirespec.RawResponse( + statusCode = response.status, + headers = mapOf(), + body = null, + ) + } + + fun fromResponse(serialization: Wirespec.Deserializer, response: Wirespec.RawResponse): Response<*> = + when (response.statusCode) { + 200 -> Response200( + body = serialization.deserialize(requireNotNull(response.body) { "body is null" }, typeOf()), + ) + else -> error("Cannot match response with status: ${response.statusCode}") + } + + interface Handler: Wirespec.Handler { + @org.springframework.web.bind.annotation.PostMapping("/user/createWithList") + +suspend fun createUsersWithListInput(request: Request): Response<*> + companion object: Wirespec.Server>, Wirespec.Client> { + override val pathTemplate = "/user/createWithList" + override val method = "POST" + override fun server(serialization: Wirespec.Serialization) = object : Wirespec.ServerEdge> { + override fun from(request: Wirespec.RawRequest) = fromRequest(serialization, request) + override fun to(response: Response<*>) = toResponse(serialization, response) + } + override fun client(serialization: Wirespec.Serialization) = object : Wirespec.ClientEdge> { + override fun to(request: Request) = toRequest(serialization, request) + override fun from(response: Wirespec.RawResponse) = fromResponse(serialization, response) + } + } + } +} +object LoginUserEndpoint : Wirespec.Endpoint { + data object Path : Wirespec.Path + + data class Queries( + val username: String?, + val password: String?, + ) : Wirespec.Queries + + data object Headers : Wirespec.Request.Headers + + class Request( + username: String?, password: String? + ) : Wirespec.Request { + override val path = Path + override val method = Wirespec.Method.GET + override val queries = Queries(username, password) + override val headers = Headers + override val body = Unit + } + + fun toRequest(serialization: Wirespec.Serializer, request: Request): Wirespec.RawRequest = + Wirespec.RawRequest( + path = listOf("user", "login"), + method = request.method.name, + queries = listOf(request.queries.username?.let{"username" to serialization.serialize(it, typeOf())}, request.queries.password?.let{"password" to serialization.serialize(it, typeOf())}).filterNotNull().toMap(), + headers = emptyMap(), + body = serialization.serialize(request.body, typeOf()), + ) + + fun fromRequest(serialization: Wirespec.Deserializer, request: Wirespec.RawRequest): Request = + Request( + username = request.queries["username"]?.let{ serialization.deserialize(it, typeOf()) }, password = request.queries["password"]?.let{ serialization.deserialize(it, typeOf()) } + ) + + sealed interface Response : Wirespec.Response + sealed interface Response2XX : Response + sealed interface Response4XX : Response + + data class Response200(override val body: String) : Response2XX { + override val status = 200 + override val headers = Headers + data object Headers : Wirespec.Response.Headers + } + data class Response400(override val body: Unit) : Response4XX { + override val status = 400 + override val headers = Headers + data object Headers : Wirespec.Response.Headers + } + + fun toResponse(serialization: Wirespec.Serializer, response: Response<*>): Wirespec.RawResponse = + when(response) { + is Response200 -> Wirespec.RawResponse( + statusCode = response.status, + headers = mapOf(), + body = serialization.serialize(response.body, typeOf()), + ) + is Response400 -> Wirespec.RawResponse( + statusCode = response.status, + headers = mapOf(), + body = null, + ) + } + + fun fromResponse(serialization: Wirespec.Deserializer, response: Wirespec.RawResponse): Response<*> = + when (response.statusCode) { + 200 -> Response200( + body = serialization.deserialize(requireNotNull(response.body) { "body is null" }, typeOf()), + ) + 400 -> Response400( + body = serialization.deserialize(requireNotNull(response.body) { "body is null" }, typeOf()), + ) + else -> error("Cannot match response with status: ${response.statusCode}") + } + + interface Handler: Wirespec.Handler { + @org.springframework.web.bind.annotation.GetMapping("/user/login") + +suspend fun loginUser(request: Request): Response<*> + companion object: Wirespec.Server>, Wirespec.Client> { + override val pathTemplate = "/user/login" + override val method = "GET" + override fun server(serialization: Wirespec.Serialization) = object : Wirespec.ServerEdge> { + override fun from(request: Wirespec.RawRequest) = fromRequest(serialization, request) + override fun to(response: Response<*>) = toResponse(serialization, response) + } + override fun client(serialization: Wirespec.Serialization) = object : Wirespec.ClientEdge> { + override fun to(request: Request) = toRequest(serialization, request) + override fun from(response: Wirespec.RawResponse) = fromResponse(serialization, response) + } + } + } +} +object LogoutUserEndpoint : Wirespec.Endpoint { + data object Path : Wirespec.Path + + data object Queries : Wirespec.Queries + + data object Headers : Wirespec.Request.Headers + + object Request : Wirespec.Request { + override val path = Path + override val method = Wirespec.Method.GET + override val queries = Queries + override val headers = Headers + override val body = Unit + } + + fun toRequest(serialization: Wirespec.Serializer, request: Request): Wirespec.RawRequest = + Wirespec.RawRequest( + path = listOf("user", "logout"), + method = request.method.name, + queries = emptyMap(), + headers = emptyMap(), + body = serialization.serialize(request.body, typeOf()), + ) + + fun fromRequest(serialization: Wirespec.Deserializer, request: Wirespec.RawRequest): Request = + Request + + sealed interface Response : Wirespec.Response + sealed interface ResponsedXX : Response + + data class Responsedefault(override val body: Unit) : ResponsedXX { + override val status = 200 + override val headers = Headers + data object Headers : Wirespec.Response.Headers + } + + fun toResponse(serialization: Wirespec.Serializer, response: Response<*>): Wirespec.RawResponse = + when(response) { + is Responsedefault -> Wirespec.RawResponse( + statusCode = response.status, + headers = mapOf(), + body = null, + ) + } + + fun fromResponse(serialization: Wirespec.Deserializer, response: Wirespec.RawResponse): Response<*> = + when (response.statusCode) { + + else -> error("Cannot match response with status: ${response.statusCode}") + } + + interface Handler: Wirespec.Handler { + @org.springframework.web.bind.annotation.GetMapping("/user/logout") + +suspend fun logoutUser(request: Request): Response<*> + companion object: Wirespec.Server>, Wirespec.Client> { + override val pathTemplate = "/user/logout" + override val method = "GET" + override fun server(serialization: Wirespec.Serialization) = object : Wirespec.ServerEdge> { + override fun from(request: Wirespec.RawRequest) = fromRequest(serialization, request) + override fun to(response: Response<*>) = toResponse(serialization, response) + } + override fun client(serialization: Wirespec.Serialization) = object : Wirespec.ClientEdge> { + override fun to(request: Request) = toRequest(serialization, request) + override fun from(response: Wirespec.RawResponse) = fromResponse(serialization, response) + } + } + } +} +object GetUserByNameEndpoint : Wirespec.Endpoint { + data class Path( + val username: String, + ) : Wirespec.Path + + data object Queries : Wirespec.Queries + + data object Headers : Wirespec.Request.Headers + + class Request( + username: String + ) : Wirespec.Request { + override val path = Path(username) + override val method = Wirespec.Method.GET + override val queries = Queries + override val headers = Headers + override val body = Unit + } + + fun toRequest(serialization: Wirespec.Serializer, request: Request): Wirespec.RawRequest = + Wirespec.RawRequest( + path = listOf("user", request.path.username.let{serialization.serialize(it, typeOf())}), + method = request.method.name, + queries = emptyMap(), + headers = emptyMap(), + body = serialization.serialize(request.body, typeOf()), + ) + + fun fromRequest(serialization: Wirespec.Deserializer, request: Wirespec.RawRequest): Request = + Request( + username = serialization.deserialize(request.path[1], typeOf()) + ) + + sealed interface Response : Wirespec.Response + sealed interface Response2XX : Response + sealed interface Response4XX : Response + + data class Response200(override val body: User) : Response2XX { + override val status = 200 + override val headers = Headers + data object Headers : Wirespec.Response.Headers + } + data class Response400(override val body: Unit) : Response4XX { + override val status = 400 + override val headers = Headers + data object Headers : Wirespec.Response.Headers + } + data class Response404(override val body: Unit) : Response4XX { + override val status = 404 + override val headers = Headers + data object Headers : Wirespec.Response.Headers + } + + fun toResponse(serialization: Wirespec.Serializer, response: Response<*>): Wirespec.RawResponse = + when(response) { + is Response200 -> Wirespec.RawResponse( + statusCode = response.status, + headers = mapOf(), + body = serialization.serialize(response.body, typeOf()), + ) + is Response400 -> Wirespec.RawResponse( + statusCode = response.status, + headers = mapOf(), + body = null, + ) + is Response404 -> Wirespec.RawResponse( + statusCode = response.status, + headers = mapOf(), + body = null, + ) + } + + fun fromResponse(serialization: Wirespec.Deserializer, response: Wirespec.RawResponse): Response<*> = + when (response.statusCode) { + 200 -> Response200( + body = serialization.deserialize(requireNotNull(response.body) { "body is null" }, typeOf()), + ) + 400 -> Response400( + body = serialization.deserialize(requireNotNull(response.body) { "body is null" }, typeOf()), + ) + 404 -> Response404( + body = serialization.deserialize(requireNotNull(response.body) { "body is null" }, typeOf()), + ) + else -> error("Cannot match response with status: ${response.statusCode}") + } + + interface Handler: Wirespec.Handler { + @org.springframework.web.bind.annotation.GetMapping("/user/{username}") + +suspend fun getUserByName(request: Request): Response<*> + companion object: Wirespec.Server>, Wirespec.Client> { + override val pathTemplate = "/user/{username}" + override val method = "GET" + override fun server(serialization: Wirespec.Serialization) = object : Wirespec.ServerEdge> { + override fun from(request: Wirespec.RawRequest) = fromRequest(serialization, request) + override fun to(response: Response<*>) = toResponse(serialization, response) + } + override fun client(serialization: Wirespec.Serialization) = object : Wirespec.ClientEdge> { + override fun to(request: Request) = toRequest(serialization, request) + override fun from(response: Wirespec.RawResponse) = fromResponse(serialization, response) + } + } + } +} +object UpdateUserEndpoint : Wirespec.Endpoint { + data class Path( + val username: String, + ) : Wirespec.Path + + data object Queries : Wirespec.Queries + + data object Headers : Wirespec.Request.Headers + + class Request( + username: String, + override val body: User, + ) : Wirespec.Request { + override val path = Path(username) + override val method = Wirespec.Method.PUT + override val queries = Queries + override val headers = Headers + } + + fun toRequest(serialization: Wirespec.Serializer, request: Request): Wirespec.RawRequest = + Wirespec.RawRequest( + path = listOf("user", request.path.username.let{serialization.serialize(it, typeOf())}), + method = request.method.name, + queries = emptyMap(), + headers = emptyMap(), + body = serialization.serialize(request.body, typeOf()), + ) + + fun fromRequest(serialization: Wirespec.Deserializer, request: Wirespec.RawRequest): Request = + Request( + username = serialization.deserialize(request.path[1], typeOf()), + body = serialization.deserialize(requireNotNull(request.body) { "body is null" }, typeOf()), + ) + + sealed interface Response : Wirespec.Response + sealed interface ResponsedXX : Response + + data class Responsedefault(override val body: Unit) : ResponsedXX { + override val status = 200 + override val headers = Headers + data object Headers : Wirespec.Response.Headers + } + + fun toResponse(serialization: Wirespec.Serializer, response: Response<*>): Wirespec.RawResponse = + when(response) { + is Responsedefault -> Wirespec.RawResponse( + statusCode = response.status, + headers = mapOf(), + body = null, + ) + } + + fun fromResponse(serialization: Wirespec.Deserializer, response: Wirespec.RawResponse): Response<*> = + when (response.statusCode) { + + else -> error("Cannot match response with status: ${response.statusCode}") + } + + interface Handler: Wirespec.Handler { + @org.springframework.web.bind.annotation.PutMapping("/user/{username}") + +suspend fun updateUser(request: Request): Response<*> + companion object: Wirespec.Server>, Wirespec.Client> { + override val pathTemplate = "/user/{username}" + override val method = "PUT" + override fun server(serialization: Wirespec.Serialization) = object : Wirespec.ServerEdge> { + override fun from(request: Wirespec.RawRequest) = fromRequest(serialization, request) + override fun to(response: Response<*>) = toResponse(serialization, response) + } + override fun client(serialization: Wirespec.Serialization) = object : Wirespec.ClientEdge> { + override fun to(request: Request) = toRequest(serialization, request) + override fun from(response: Wirespec.RawResponse) = fromResponse(serialization, response) + } + } + } +} +object DeleteUserEndpoint : Wirespec.Endpoint { + data class Path( + val username: String, + ) : Wirespec.Path + + data object Queries : Wirespec.Queries + + data object Headers : Wirespec.Request.Headers + + class Request( + username: String + ) : Wirespec.Request { + override val path = Path(username) + override val method = Wirespec.Method.DELETE + override val queries = Queries + override val headers = Headers + override val body = Unit + } + + fun toRequest(serialization: Wirespec.Serializer, request: Request): Wirespec.RawRequest = + Wirespec.RawRequest( + path = listOf("user", request.path.username.let{serialization.serialize(it, typeOf())}), + method = request.method.name, + queries = emptyMap(), + headers = emptyMap(), + body = serialization.serialize(request.body, typeOf()), + ) + + fun fromRequest(serialization: Wirespec.Deserializer, request: Wirespec.RawRequest): Request = + Request( + username = serialization.deserialize(request.path[1], typeOf()) + ) + + sealed interface Response : Wirespec.Response + sealed interface Response4XX : Response + + data class Response400(override val body: Unit) : Response4XX { + override val status = 400 + override val headers = Headers + data object Headers : Wirespec.Response.Headers + } + data class Response404(override val body: Unit) : Response4XX { + override val status = 404 + override val headers = Headers + data object Headers : Wirespec.Response.Headers + } + + fun toResponse(serialization: Wirespec.Serializer, response: Response<*>): Wirespec.RawResponse = + when(response) { + is Response400 -> Wirespec.RawResponse( + statusCode = response.status, + headers = mapOf(), + body = null, + ) + is Response404 -> Wirespec.RawResponse( + statusCode = response.status, + headers = mapOf(), + body = null, + ) + } + + fun fromResponse(serialization: Wirespec.Deserializer, response: Wirespec.RawResponse): Response<*> = + when (response.statusCode) { + 400 -> Response400( + body = serialization.deserialize(requireNotNull(response.body) { "body is null" }, typeOf()), + ) + 404 -> Response404( + body = serialization.deserialize(requireNotNull(response.body) { "body is null" }, typeOf()), + ) + else -> error("Cannot match response with status: ${response.statusCode}") + } + + interface Handler: Wirespec.Handler { + @org.springframework.web.bind.annotation.DeleteMapping("/user/{username}") + +suspend fun deleteUser(request: Request): Response<*> + companion object: Wirespec.Server>, Wirespec.Client> { + override val pathTemplate = "/user/{username}" + override val method = "DELETE" + override fun server(serialization: Wirespec.Serialization) = object : Wirespec.ServerEdge> { + override fun from(request: Wirespec.RawRequest) = fromRequest(serialization, request) + override fun to(response: Response<*>) = toResponse(serialization, response) + } + override fun client(serialization: Wirespec.Serialization) = object : Wirespec.ClientEdge> { + override fun to(request: Request) = toRequest(serialization, request) + override fun from(response: Wirespec.RawResponse) = fromResponse(serialization, response) + } + } + } +} +enum class FindPetsByStatusParameterStatus (override val label: String): Wirespec.Enum { + available("available"), + pending("pending"), + sold("sold"); + override fun toString(): String { + return label + } +} +data class Order( + val id: Long?, + val petId: Long?, + val quantity: Long?, + val shipDate: String?, + val status: OrderStatus?, + val complete: Boolean? +) +enum class OrderStatus (override val label: String): Wirespec.Enum { + placed("placed"), + approved("approved"), + delivered("delivered"); + override fun toString(): String { + return label + } +} +data class Customer( + val id: Long?, + val username: String?, + val address: List
? +) +data class Address( + val street: String?, + val city: String?, + val state: String?, + val zip: String? +) +data class Category( + val id: Long?, + val name: String? +) +data class User( + val id: Long?, + val username: String?, + val firstName: String?, + val lastName: String?, + val email: String?, + val password: String?, + val phone: String?, + val userStatus: Long? +) +data class Tag( + val id: Long?, + val name: String? +) +data class Pet( + val id: Long?, + val name: String, + val category: Category?, + val photoUrls: List, + val tags: List?, + val status: PetStatus? +) +enum class PetStatus (override val label: String): Wirespec.Enum { + available("available"), + pending("pending"), + sold("sold"); + override fun toString(): String { + return label + } +} +data class ApiResponse( + val code: Long?, + val type: String?, + val message: String? +) diff --git a/src/integration/spring/src/jvmTest/kotlin/community/flock/wirespec/integration/spring/kotlin/it/IntegrationTest.kt b/src/integration/spring/src/jvmTest/kotlin/community/flock/wirespec/integration/spring/kotlin/it/IntegrationTest.kt new file mode 100644 index 00000000..c08a7fba --- /dev/null +++ b/src/integration/spring/src/jvmTest/kotlin/community/flock/wirespec/integration/spring/kotlin/it/IntegrationTest.kt @@ -0,0 +1,96 @@ +package community.flock.wirespec.integration.spring.kotlin.it + + +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.module.kotlin.readValue +import community.flock.wirespec.integration.spring.kotlin.application.Application +import community.flock.wirespec.integration.spring.kotlin.application.Service +import community.flock.wirespec.integration.spring.kotlin.generated.Pet +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test + +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.http.MediaType +import org.springframework.test.web.servlet.MockMvc +import org.springframework.test.web.servlet.ResultActions +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.asyncDispatch +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put +import org.springframework.test.web.servlet.result.MockMvcResultHandlers.print +import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath +import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status + + +@SpringBootTest(classes = [Application::class]) +@AutoConfigureMockMvc +class IntegrationTest { + + @Autowired + lateinit var mockMvc: MockMvc + + @Autowired + lateinit var objectMapper: ObjectMapper + + @Autowired + lateinit var service: Service + + + @Test + fun crudIntegrationTest() { + + val pet = Pet( + id = 1, + name = "Dog", + photoUrls = listOf(), + category = null, + tags = null, + status = null, + ) + + mockMvc + .perform( + post("/pet") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(pet)) + ) + .asyncDispatch() + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.name").value("Dog")) + + mockMvc + .perform( + put("/pet") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(pet.copy(name = "Cat"))) + ) + .asyncDispatch() + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.name").value("Cat")) + + mockMvc + .perform(get("/pet/${pet.id}")) + .asyncDispatch() + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.name").value("Cat")) + + + mockMvc + .perform(delete("/pet/${pet.id}")) + .asyncDispatch() + .andDo(print()) + .andExpect(status().isBadRequest()) + + assertEquals(0, service.list.size) + } + + fun ResultActions.asyncDispatch(): ResultActions = mockMvc.perform(asyncDispatch(this.andReturn())) + +} + diff --git a/src/integration/spring/src/jvmTest/resources/application.yml b/src/integration/spring/src/jvmTest/resources/application.yml new file mode 100644 index 00000000..c56eee69 --- /dev/null +++ b/src/integration/spring/src/jvmTest/resources/application.yml @@ -0,0 +1,3 @@ +logging: + level: + root: info \ No newline at end of file diff --git a/src/integration/spring/src/jvmTest/resources/petstore.json b/src/integration/spring/src/jvmTest/resources/petstore.json new file mode 100644 index 00000000..d9e56db2 --- /dev/null +++ b/src/integration/spring/src/jvmTest/resources/petstore.json @@ -0,0 +1,1148 @@ +{ + "openapi": "3.0.2", + "info": { + "title": "Swagger Petstore - OpenAPI 3.0", + "description": "This is a sample Pet Store Server based on the OpenAPI 3.0 specification. You can find out more about\nSwagger at [http://swagger.io](http://swagger.io). In the third iteration of the pet store, we've switched to the design first approach!\nYou can now help us improve the API whether it's by making changes to the definition itself or to the code.\nThat way, with time, we can improve the API in general, and expose some of the new features in OAS3.\n\nSome useful links:\n- [The Pet Store repository](https://github.com/swagger-api/swagger-petstore)\n- [The source API definition for the Pet Store](https://github.com/swagger-api/swagger-petstore/blob/master/src/main/resources/openapi.yaml)", + "termsOfService": "http://swagger.io/terms/", + "contact": { + "email": "apiteam@swagger.io" + }, + "license": { + "name": "Apache 2.0", + "url": "http://www.apache.org/licenses/LICENSE-2.0.html" + }, + "version": "1.0.17" + }, + "externalDocs": { + "description": "Find out more about Swagger", + "url": "http://swagger.io" + }, + "tags": [ + { + "name": "pet", + "description": "Everything about your Pets", + "externalDocs": { + "description": "Find out more", + "url": "http://swagger.io" + } + }, + { + "name": "store", + "description": "Access to Petstore orders", + "externalDocs": { + "description": "Find out more about our store", + "url": "http://swagger.io" + } + }, + { + "name": "user", + "description": "Operations about user" + } + ], + "paths": { + "/pet": { + "put": { + "tags": [ + "pet" + ], + "summary": "Update an existing pet", + "description": "Update an existing pet by Id", + "operationId": "updatePet", + "requestBody": { + "description": "Update an existent pet in the store", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + } + } + }, + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Pet not found" + }, + "405": { + "description": "Validation exception" + } + } + }, + "post": { + "tags": [ + "pet" + ], + "summary": "Add a new pet to the store", + "description": "Add a new pet to the store", + "operationId": "addPet", + "requestBody": { + "description": "Create a new pet in the store", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + } + } + }, + "405": { + "description": "Invalid input" + } + } + } + }, + "/pet/findByStatus": { + "get": { + "tags": [ + "pet" + ], + "summary": "Finds Pets by status", + "description": "Multiple status values can be provided with comma separated strings", + "operationId": "findPetsByStatus", + "parameters": [ + { + "name": "status", + "in": "query", + "description": "Status values that need to be considered for filter", + "required": false, + "explode": true, + "schema": { + "type": "string", + "default": "available", + "enum": [ + "available", + "pending", + "sold" + ] + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/xml": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Pet" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Pet" + } + } + } + } + }, + "400": { + "description": "Invalid status value" + } + } + } + }, + "/pet/findByTags": { + "get": { + "tags": [ + "pet" + ], + "summary": "Finds Pets by tags", + "description": "Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.", + "operationId": "findPetsByTags", + "parameters": [ + { + "name": "tags", + "in": "query", + "description": "Tags to filter by", + "required": false, + "explode": true, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/xml": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Pet" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Pet" + } + } + } + } + }, + "400": { + "description": "Invalid tag value" + } + } + } + }, + "/pet/{petId}": { + "get": { + "tags": [ + "pet" + ], + "summary": "Find pet by ID", + "description": "Returns a single pet", + "operationId": "getPetById", + "parameters": [ + { + "name": "petId", + "in": "path", + "description": "ID of pet to return", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + } + } + }, + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Pet not found" + } + } + }, + "post": { + "tags": [ + "pet" + ], + "summary": "Updates a pet in the store with form data", + "description": "", + "operationId": "updatePetWithForm", + "parameters": [ + { + "name": "petId", + "in": "path", + "description": "ID of pet that needs to be updated", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "name", + "in": "query", + "description": "Name of pet that needs to be updated", + "schema": { + "type": "string" + } + }, + { + "name": "status", + "in": "query", + "description": "Status of pet that needs to be updated", + "schema": { + "type": "string" + } + } + ], + "responses": { + "405": { + "description": "Invalid input" + } + } + }, + "delete": { + "tags": [ + "pet" + ], + "summary": "Deletes a pet", + "description": "", + "operationId": "deletePet", + "parameters": [ + { + "name": "api_key", + "in": "header", + "description": "", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "petId", + "in": "path", + "description": "Pet id to delete", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "400": { + "description": "Invalid pet value" + } + } + } + }, + "/pet/{petId}/uploadImage": { + "post": { + "tags": [ + "pet" + ], + "summary": "uploads an image", + "description": "", + "operationId": "uploadFile", + "parameters": [ + { + "name": "petId", + "in": "path", + "description": "ID of pet to update", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "additionalMetadata", + "in": "query", + "description": "Additional Metadata", + "required": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/octet-stream": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse" + } + } + } + } + } + } + }, + "/store/inventory": { + "get": { + "tags": [ + "store" + ], + "summary": "Returns pet inventories by status", + "description": "Returns a map of status codes to quantities", + "operationId": "getInventory", + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": { + "type": "integer", + "format": "int32" + } + } + } + } + } + } + } + }, + "/store/order": { + "post": { + "tags": [ + "store" + ], + "summary": "Place an order for a pet", + "description": "Place a new order in the store", + "operationId": "placeOrder", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Order" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Order" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/Order" + } + } + } + }, + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Order" + } + } + } + }, + "405": { + "description": "Invalid input" + } + } + } + }, + "/store/order/{orderId}": { + "get": { + "tags": [ + "store" + ], + "summary": "Find purchase order by ID", + "description": "For valid response try integer IDs with value <= 5 or > 10. Other values will generate exceptions.", + "operationId": "getOrderById", + "parameters": [ + { + "name": "orderId", + "in": "path", + "description": "ID of order that needs to be fetched", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Order" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/Order" + } + } + } + }, + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Order not found" + } + } + }, + "delete": { + "tags": [ + "store" + ], + "summary": "Delete purchase order by ID", + "description": "For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors", + "operationId": "deleteOrder", + "parameters": [ + { + "name": "orderId", + "in": "path", + "description": "ID of the order that needs to be deleted", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Order not found" + } + } + } + }, + "/user": { + "post": { + "tags": [ + "user" + ], + "summary": "Create user", + "description": "This can only be done by the logged in user.", + "operationId": "createUser", + "requestBody": { + "description": "Created user object", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/User" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + } + }, + "responses": { + "default": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + } + } + } + } + }, + "/user/createWithList": { + "post": { + "tags": [ + "user" + ], + "summary": "Creates list of users with given input array", + "description": "Creates list of users with given input array", + "operationId": "createUsersWithListInput", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/User" + } + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/xml": { + "schema": { + "$ref": "#/components/schemas/User" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + } + }, + "default": { + "description": "successful operation" + } + } + } + }, + "/user/login": { + "get": { + "tags": [ + "user" + ], + "summary": "Logs user into the system", + "description": "", + "operationId": "loginUser", + "parameters": [ + { + "name": "username", + "in": "query", + "description": "The user name for login", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "password", + "in": "query", + "description": "The password for login in clear text", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "headers": { + "X-Rate-Limit": { + "description": "calls per hour allowed by the user", + "schema": { + "type": "integer", + "format": "int32" + } + }, + "X-Expires-After": { + "description": "date in UTC when token expires", + "schema": { + "type": "string", + "format": "date-time" + } + } + }, + "content": { + "application/xml": { + "schema": { + "type": "string" + } + }, + "application/json": { + "schema": { + "type": "string" + } + } + } + }, + "400": { + "description": "Invalid username/password supplied" + } + } + } + }, + "/user/logout": { + "get": { + "tags": [ + "user" + ], + "summary": "Logs out current logged in user session", + "description": "", + "operationId": "logoutUser", + "parameters": [], + "responses": { + "default": { + "description": "successful operation" + } + } + } + }, + "/user/{username}": { + "get": { + "tags": [ + "user" + ], + "summary": "Get user by user name", + "description": "", + "operationId": "getUserByName", + "parameters": [ + { + "name": "username", + "in": "path", + "description": "The name that needs to be fetched. Use user1 for testing. ", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/xml": { + "schema": { + "$ref": "#/components/schemas/User" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + } + }, + "400": { + "description": "Invalid username supplied" + }, + "404": { + "description": "User not found" + } + } + }, + "put": { + "tags": [ + "user" + ], + "summary": "Update user", + "description": "This can only be done by the logged in user.", + "operationId": "updateUser", + "parameters": [ + { + "name": "username", + "in": "path", + "description": "name that need to be deleted", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "Update an existent user in the store", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/User" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + } + }, + "responses": { + "default": { + "description": "successful operation" + } + } + }, + "delete": { + "tags": [ + "user" + ], + "summary": "Delete user", + "description": "This can only be done by the logged in user.", + "operationId": "deleteUser", + "parameters": [ + { + "name": "username", + "in": "path", + "description": "The name that needs to be deleted", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "400": { + "description": "Invalid username supplied" + }, + "404": { + "description": "User not found" + } + } + } + } + }, + "components": { + "schemas": { + "Order": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64", + "example": 10 + }, + "petId": { + "type": "integer", + "format": "int64", + "example": 198772 + }, + "quantity": { + "type": "integer", + "format": "int32", + "example": 7 + }, + "shipDate": { + "type": "string", + "format": "date-time" + }, + "status": { + "type": "string", + "description": "Order Status", + "example": "approved", + "enum": [ + "placed", + "approved", + "delivered" + ] + }, + "complete": { + "type": "boolean" + } + }, + "xml": { + "name": "order" + } + }, + "Customer": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64", + "example": 100000 + }, + "username": { + "type": "string", + "example": "fehguy" + }, + "address": { + "type": "array", + "xml": { + "name": "addresses", + "wrapped": true + }, + "items": { + "$ref": "#/components/schemas/Address" + } + } + }, + "xml": { + "name": "customer" + } + }, + "Address": { + "type": "object", + "properties": { + "street": { + "type": "string", + "example": "437 Lytton" + }, + "city": { + "type": "string", + "example": "Palo Alto" + }, + "state": { + "type": "string", + "example": "CA" + }, + "zip": { + "type": "string", + "example": "94301" + } + }, + "xml": { + "name": "address" + } + }, + "Category": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "type": "string", + "example": "Dogs" + } + }, + "xml": { + "name": "category" + } + }, + "User": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64", + "example": 10 + }, + "username": { + "type": "string", + "example": "theUser" + }, + "firstName": { + "type": "string", + "example": "John" + }, + "lastName": { + "type": "string", + "example": "James" + }, + "email": { + "type": "string", + "example": "john@email.com" + }, + "password": { + "type": "string", + "example": "12345" + }, + "phone": { + "type": "string", + "example": "12345" + }, + "userStatus": { + "type": "integer", + "description": "User Status", + "format": "int32", + "example": 1 + } + }, + "xml": { + "name": "user" + } + }, + "Tag": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + } + }, + "xml": { + "name": "tag" + } + }, + "Pet": { + "required": [ + "name", + "photoUrls" + ], + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64", + "example": 10 + }, + "name": { + "type": "string", + "example": "doggie" + }, + "category": { + "$ref": "#/components/schemas/Category" + }, + "photoUrls": { + "type": "array", + "xml": { + "wrapped": true + }, + "items": { + "type": "string", + "xml": { + "name": "photoUrl" + } + } + }, + "tags": { + "type": "array", + "xml": { + "wrapped": true + }, + "items": { + "$ref": "#/components/schemas/Tag" + } + }, + "status": { + "type": "string", + "description": "pet status in the store", + "enum": [ + "available", + "pending", + "sold" + ] + } + }, + "xml": { + "name": "pet" + } + }, + "ApiResponse": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "type": { + "type": "string" + }, + "message": { + "type": "string" + } + }, + "xml": { + "name": "##default" + } + } + }, + "requestBodies": { + "Pet": { + "description": "Pet object that needs to be added to the store", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + } + } + }, + "UserArray": { + "description": "List of user object", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/User" + } + } + } + } + } + }, + "securitySchemes": { + "petstore_auth": { + "type": "oauth2", + "flows": { + "implicit": { + "authorizationUrl": "https://petstore3.swagger.io/oauth/authorize", + "scopes": { + "write:pets": "modify pets in your account", + "read:pets": "read your pets" + } + } + } + }, + "api_key": { + "type": "apiKey", + "name": "api_key", + "in": "header" + } + } + } +} \ No newline at end of file diff --git a/src/integration/wirespec/build.gradle.kts b/src/integration/wirespec/build.gradle.kts index 3c8e589c..8d070915 100644 --- a/src/integration/wirespec/build.gradle.kts +++ b/src/integration/wirespec/build.gradle.kts @@ -20,16 +20,9 @@ kotlin { } } sourceSets { - commonMain { - dependencies { - - } - } commonTest { dependencies { - implementation(kotlin("test-common")) - implementation(kotlin("test-annotations-common")) - implementation(kotlin("test-junit")) + implementation(libs.bundles.kotlin.test) } } val jvmMain by getting { diff --git a/src/integration/wirespec/src/jvmMain/java/community/flock/wirespec/java/Wirespec.java b/src/integration/wirespec/src/jvmMain/java/community/flock/wirespec/java/Wirespec.java index e05dcae8..927ac4ce 100644 --- a/src/integration/wirespec/src/jvmMain/java/community/flock/wirespec/java/Wirespec.java +++ b/src/integration/wirespec/src/jvmMain/java/community/flock/wirespec/java/Wirespec.java @@ -2,67 +2,51 @@ import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; +import java.util.List; +import java.util.Map; public interface Wirespec { - interface Enum { - } - - interface Refined { - String getValue(); - } - - interface Endpoint { - } - - enum Method {GET, PUT, POST, DELETE, OPTIONS, HEAD, PATCH, TRACE} - - record Content(String type, T body) { - } - - interface Request { - String getPath(); - - Method getMethod(); - - java.util.Map> getQuery(); - - java.util.Map> getHeaders(); - - Content getContent(); - } - - interface Response { - int getStatus(); - - java.util.Map> getHeaders(); - - Content getContent(); - } - - interface ContentMapper { - Content read(Content content, Type valueType); - - Content write(Content content); - } - + interface Enum {} + interface Endpoint {} + interface Refined { String getValue(); } + interface Path {} + interface Queries {} + interface Headers {} + interface Handler {} + interface ServerEdge, Res extends Response> { + Req from(RawRequest request); + RawResponse to(Res response); + } + interface ClientEdge, Res extends Response> { + RawRequest to(Req request); + Res from(RawResponse response); + } + interface Client, Res extends Response> { + String getPathTemplate(); + String getMethod(); + ClientEdge getClient(Serialization serialization); + } + interface Server, Res extends Response> { + String getPathTemplate(); + String getMethod(); + ServerEdge getServer(Serialization serialization); + } + enum Method { GET, PUT, POST, DELETE, OPTIONS, HEAD, PATCH, TRACE } + interface Request { Path getPath(); Method getMethod(); Queries getQueries(); Headers getHeaders(); T getBody(); interface Headers extends Wirespec.Headers {} } + interface Response { int getStatus(); Headers getHeaders(); T getBody(); interface Headers extends Wirespec.Headers {} } + interface Serialization extends Serializer, Deserializer {} + interface Serializer { RAW serialize(T t, Type type); } + interface Deserializer { T deserialize(RAW raw, Type type); } + record RawRequest(String method, List path, Map queries, Map headers, String body) {} + record RawResponse(int statusCode, Map headers, String body) {} static Type getType(final Class type, final boolean isIterable) { - if (isIterable) { + if(isIterable) { return new ParameterizedType() { - public Type getRawType() { - return java.util.List.class; - } - - public Type[] getActualTypeArguments() { - Class[] types = {type}; - return types; - } - - public Type getOwnerType() { - return null; - } + public Type getRawType() { return java.util.List.class; } + public Type[] getActualTypeArguments() { return new Class[]{type}; } + public Type getOwnerType() { return null; } }; - } else { - return type; } + else { return type; } } } diff --git a/src/integration/wirespec/src/jvmMain/kotlin/community/flock/wirespec/kotlin/Wirespec.kt b/src/integration/wirespec/src/jvmMain/kotlin/community/flock/wirespec/kotlin/Wirespec.kt index 769b12e4..3f887072 100644 --- a/src/integration/wirespec/src/jvmMain/kotlin/community/flock/wirespec/kotlin/Wirespec.kt +++ b/src/integration/wirespec/src/jvmMain/kotlin/community/flock/wirespec/kotlin/Wirespec.kt @@ -3,7 +3,9 @@ package community.flock.wirespec.kotlin import kotlin.reflect.KType object Wirespec { - interface Enum + interface Enum { + val label:String + } interface Endpoint interface Refined { val value: String @@ -14,13 +16,13 @@ object Wirespec { interface Headers interface Handler interface ServerEdge, Res : Response<*>> { - fun consume(request: RawRequest): Req - fun produce(response: Res): RawResponse + fun from(request: RawRequest): Req + fun to(response: Res): RawResponse } interface ClientEdge, Res : Response<*>> { - fun internalize(response: RawResponse): Res - fun externalize(request: Req): RawRequest + fun from(response: RawResponse): Res + fun to(request: Req): RawRequest } interface Client, Res : Response<*>> { @@ -66,10 +68,10 @@ object Wirespec { data class RawRequest( val method: String, val path: List, - val queries: Map>, - val headers: Map>, + val queries: Map, + val headers: Map, val body: String? ) - data class RawResponse(val statusCode: Int, val headers: Map>, val body: String?) + data class RawResponse(val statusCode: Int, val headers: Map, val body: String?) } diff --git a/src/plugin/arguments/build.gradle.kts b/src/plugin/arguments/build.gradle.kts index 7a5c89d2..88ad4c2b 100644 --- a/src/plugin/arguments/build.gradle.kts +++ b/src/plugin/arguments/build.gradle.kts @@ -28,7 +28,7 @@ kotlin { } val commonTest by getting { dependencies { - implementation(kotlin("test")) + implementation(libs.kotlin.test) implementation(libs.bundles.kotest) } } diff --git a/src/plugin/cli/build.gradle.kts b/src/plugin/cli/build.gradle.kts index e74953ef..dd49a04a 100644 --- a/src/plugin/cli/build.gradle.kts +++ b/src/plugin/cli/build.gradle.kts @@ -53,7 +53,7 @@ kotlin { } val commonTest by getting { dependencies { - implementation(kotlin("test")) + implementation(libs.kotlin.test) implementation(libs.bundles.kotest) } } diff --git a/src/plugin/maven/src/main/kotlin/CustomMojo.kt b/src/plugin/maven/src/main/kotlin/CustomMojo.kt index e92ec2be..b054c736 100644 --- a/src/plugin/maven/src/main/kotlin/CustomMojo.kt +++ b/src/plugin/maven/src/main/kotlin/CustomMojo.kt @@ -12,12 +12,12 @@ import community.flock.wirespec.plugin.Language import community.flock.wirespec.plugin.PackageName import community.flock.wirespec.plugin.compile import community.flock.wirespec.plugin.toDirectory -import java.io.File import org.apache.maven.plugins.annotations.LifecyclePhase import org.apache.maven.plugins.annotations.Mojo import org.apache.maven.plugins.annotations.Parameter import org.apache.maven.plugins.annotations.ResolutionScope import org.apache.maven.project.MavenProject +import java.io.File @Mojo( name = "custom", @@ -44,10 +44,18 @@ class CustomMojo : BaseMojo() { val emitterPkg = PackageName(packageName) val emitter = try { - getClassLoader(project) - .loadClass(emitterClass) - .getConstructor(Logger::class.java, Boolean::class.java) - .newInstance(logger, split) as Emitter + val clazz = getClassLoader(project).loadClass(emitterClass) + val constructor = clazz.constructors.first() + val args = constructor.parameters + .map { + when (it.type) { + String::class.java -> packageName + Boolean::class.java -> split + Logger::class.java -> logger + else -> error("Cannot map constructor parameter") + } + } + constructor.newInstance(*args.toTypedArray()) as Emitter } catch (e: Exception) { throw e.also { it.printStackTrace() } } @@ -76,6 +84,7 @@ class CustomMojo : BaseMojo() { private fun getClassLoader(project: MavenProject): ClassLoader { try { + val classpathElements = project.compileClasspathElements.apply { add(project.build.outputDirectory) add(project.build.testOutputDirectory) @@ -83,6 +92,7 @@ class CustomMojo : BaseMojo() { val urls = classpathElements.indices .map { File(classpathElements[it] as String).toURI().toURL() } .toTypedArray() + return java.net.URLClassLoader(urls, javaClass.getClassLoader()) } catch (e: Exception) { log.debug("Couldn't get the classloader.") diff --git a/src/plugin/npm/build.gradle.kts b/src/plugin/npm/build.gradle.kts index c3e61892..7881bab4 100644 --- a/src/plugin/npm/build.gradle.kts +++ b/src/plugin/npm/build.gradle.kts @@ -30,18 +30,18 @@ kotlin { sourceSets { val commonMain by getting { dependencies { - implementation(libs.kotlinx.openapi.bindings) - implementation(libs.kotlinx.serialization) implementation(project(":src:compiler:core")) implementation(project(":src:compiler:lib")) implementation(project(":src:plugin:cli")) implementation(project(":src:converter:openapi")) implementation(project(":src:tools:generator")) + implementation(libs.kotlinx.openapi.bindings) + implementation(libs.kotlinx.serialization) } } val jsMain by getting { dependencies { - implementation(kotlin("test")) + implementation(libs.kotlin.test) implementation(libs.kotlinx.resources) } } diff --git a/src/tools/generator/build.gradle.kts b/src/tools/generator/build.gradle.kts index 574be76a..0bdf6b6f 100644 --- a/src/tools/generator/build.gradle.kts +++ b/src/tools/generator/build.gradle.kts @@ -35,7 +35,7 @@ kotlin { } commonTest { dependencies { - implementation(kotlin("test")) + implementation(libs.kotlin.test) } } }