Skip to content

Commit

Permalink
Refactor request and respons mapper
Browse files Browse the repository at this point in the history
  • Loading branch information
wilmveel committed Nov 4, 2023
1 parent dde53a6 commit 337223e
Show file tree
Hide file tree
Showing 7 changed files with 84 additions and 101 deletions.
45 changes: 0 additions & 45 deletions examples/spring-boot-openapi-maven-plugin/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -142,51 +142,6 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>3.0.0</version>
<executions>
<execution>
<id>add-source</id>
<phase>generate-sources</phase>
<goals>
<goal>add-source</goal>
</goals>
<configuration>
<sources>
<source>${project.build.directory}/generated-sources</source>
</sources>
</configuration>
</execution>
</executions>
</plugin>

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<executions>
<execution>
<id>default-compile</id>
<phase>none</phase>
</execution>
<execution>
<id>default-testCompile</id>
<phase>none</phase>
</execution>
<execution>
<id>java-compile</id>
<phase>compile</phase>
<goals> <goal>compile</goal> </goals>
</execution>
<execution>
<id>java-test-compile</id>
<phase>test-compile</phase>
<goals> <goal>testCompile</goal> </goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import org.springframework.http.HttpMethod
import org.springframework.web.client.RestTemplate
import java.lang.reflect.Type
import java.net.URI
import java.util.concurrent.CompletableFuture

interface JavaPetstoreClient : AddPet, FindPetsByStatus

Expand Down Expand Up @@ -38,8 +39,8 @@ class JavaPetClientConfiguration {
object : JavaPetstoreClient {
fun <Req : Wirespec.Request<*>, Res : Wirespec.Response<*>> handle(
request: Req,
responseMapper: (Wirespec.ContentMapper<ByteArray>, Int, Map<String, List<String>>, Wirespec.Content<ByteArray>) -> Res
):Res = restTemplate.execute(
responseMapper: (Wirespec.ContentMapper<ByteArray>, Wirespec.Response<ByteArray>) -> Res
):CompletableFuture<Res> = restTemplate.execute(
URI("https://6467e16be99f0ba0a819fd68.mockapi.io${request.path}"),
HttpMethod.valueOf(request.method.name),
{ req ->
Expand All @@ -50,16 +51,21 @@ class JavaPetClientConfiguration {
{ res ->
val contentType = res.headers.contentType?.toString() ?: error("No content type")
val content = Wirespec.Content(contentType, res.body.readBytes())
responseMapper(javaContentMapper, res.statusCode.value(), res.headers, content)
val response = object :Wirespec.Response<ByteArray>{
override val status = res.statusCode.value()
override val headers = res.headers
override val content = content
}
CompletableFuture.completedFuture(responseMapper(javaContentMapper, response))
}
) ?: error("No response")


override fun addPet(request: AddPet.Request<*>): AddPet.Response<*> {
override fun addPet(request: AddPet.Request<*>): CompletableFuture<AddPet.Response<*>> {
return handle(request, AddPet::RESPONSE_MAPPER)
}

override fun findPetsByStatus(request: FindPetsByStatus.Request<*>): FindPetsByStatus.Response<*> {
override fun findPetsByStatus(request: FindPetsByStatus.Request<*>): CompletableFuture<FindPetsByStatus.Response<*>> {
return handle(request, FindPetsByStatus::RESPONSE_MAPPER)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class JavaPetstoreController(
suspend fun addPet(): Optional<Int>? {
val pet = Pet(Optional.empty(), "Petje", Optional.empty(), emptyList(), Optional.empty(), Optional.empty())
val req = AddPet.RequestApplicationJson(pet)
return when (val res = javaPetstoreClient.addPet(req)) {
return when (val res = javaPetstoreClient.addPet(req).get()) {
is AddPet.Response200ApplicationJson -> res.content?.body?.id
else -> error("No response")
}
Expand All @@ -27,7 +27,7 @@ class JavaPetstoreController(
@PostMapping
suspend fun create(@RequestBody pet: Pet): List<Int> {
val req = FindPetsByStatus.RequestVoid(Optional.of(FindPetsByStatusParameterStatus.available))
return when (val res = javaPetstoreClient.findPetsByStatus(req)) {
return when (val res = javaPetstoreClient.findPetsByStatus(req).get()) {
is FindPetsByStatus.Response200ApplicationJson -> res.content?.body?.mapNotNull { it.id.getOrNull() } ?: emptyList()
else -> error("No response")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class KotlinPetClientConfiguration {
object : KotlinPetstoreClient {
fun <Req : Wirespec.Request<*>, Res : Wirespec.Response<*>> handle(
request: Req,
responseMapper: (Wirespec.ContentMapper<ByteArray>, Int, Map<String, List<String>>, Wirespec.Content<ByteArray>) -> Res
responseMapper: (Wirespec.ContentMapper<ByteArray>, Wirespec.Response<ByteArray>) -> Res
) = restTemplate.execute(
URI("https://6467e16be99f0ba0a819fd68.mockapi.io${request.path}"),
HttpMethod.valueOf(request.method.name),
Expand All @@ -54,12 +54,12 @@ class KotlinPetClientConfiguration {
{ res ->
val contentType = res.headers.contentType?.toString() ?: error("No content type")
val content = Wirespec.Content(contentType, res.body.readBytes())
responseMapper(
kotlinContentMapper,
res.statusCode.value(),
res.headers,
content
)
val response = object :Wirespec.Response<ByteArray>{
override val status = res.statusCode.value()
override val headers = res.headers
override val content = content
}
responseMapper(kotlinContentMapper, response)
}
) ?: error("No response")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ class JavaEmitter(
""".trimMargin()

private val pkg = if (packageName.isBlank()) "" else "package $packageName;"
private fun import(ast:AST) = if (!ast.hasEndpoints()) "" else "import community.flock.wirespec.Wirespec;\n\n"
private fun import(ast: AST) =
if (!ast.hasEndpoints()) "" else "import community.flock.wirespec.Wirespec;\nimport java.util.concurrent.CompletableFuture;\n\n"

override fun emit(ast: AST): List<Pair<String, String>> = super.emit(ast)
.map { (name, result) -> name to "$pkg\n\n${import(ast)}$result" }
Expand All @@ -64,7 +65,14 @@ class JavaEmitter(
"$SPACER${if (isNullable) "java.util.Optional<${reference.emit()}>" else reference.emit()} ${identifier.emit()}"
}

override fun Type.Shape.Field.Identifier.emit() = withLogging(logger) { value }
override fun Type.Shape.Field.Identifier.emit() = withLogging(logger) {
value
.split("-")
.mapIndexed { index, s -> if (index > 0) s.firstToUpper() else s }
.joinToString("")
.sanitizeKeywords()
.sanitizeSymbols()
}

private fun Reference.emitPrimaryType() = withLogging(logger) {
when (this) {
Expand All @@ -84,7 +92,7 @@ class JavaEmitter(
}

override fun Enum.emit() = withLogging(logger) {
fun String.sanitize() = replace("-", "_").let { if(it.first().isDigit()) "_$it" else it }
fun String.sanitize() = replace("-", "_").let { if (it.first().isDigit()) "_$it" else it }
val body = """
|${SPACER}public final String label;
|${SPACER}$name(String label) {
Expand All @@ -97,7 +105,7 @@ class JavaEmitter(
|${SPACER}${SPACER}return label;
|${SPACER}}
""".trimMargin()
"public enum $name {\n${SPACER}${entries.joinToString(",\n${SPACER}"){ enum -> "${enum.sanitize()}(\"${enum}\")"}};\n${body}\n${toString}\n}\n"
"public enum $name {\n${SPACER}${entries.joinToString(",\n${SPACER}") { enum -> "${enum.sanitize()}(\"${enum}\")" }};\n${body}\n${toString}\n}\n"
}

override fun Refined.emit() = withLogging(logger) {
Expand All @@ -117,25 +125,25 @@ class JavaEmitter(
"""public interface $name {
|${SPACER}static String PATH = "${path.emitSegment()}";
|${responses.emitResponseMapper()}
|${SPACER}interface Request<T> extends Wirespec.Request<T> {}
|${SPACER}sealed interface Request<T> extends Wirespec.Request<T> {}
|${requests.joinToString("\n") { it.emit(this) }}
|${SPACER}interface Response<T> extends Wirespec.Response<T> {}
|${SPACER}sealed interface Response<T> extends Wirespec.Response<T> {}
|${
responses.map { it.status.groupStatus() }.toSet()
.joinToString("\n") { "${SPACER}interface Response${it}<T> extends Response<T>{};" }
.joinToString("\n") { "${SPACER}sealed interface Response${it}<T> extends Response<T>{};" }
}
|${
responses.filter { it.status.isInt() }.map { it.status }.toSet()
.joinToString("\n") { "${SPACER}interface Response${it}<T> extends Response${it.groupStatus()}<T>{};" }
.joinToString("\n") { "${SPACER}sealed interface Response${it}<T> extends Response${it.groupStatus()}<T>{};" }
}
|${responses.distinctBy { it.status to it.content?.type }.joinToString("\n") { it.emit() }}
|${SPACER}public Response ${name.firstToLower()}(Request request);
|${SPACER}public CompletableFuture<Response> ${name.firstToLower()}(Request request);
|}
|""".trimMargin()
}

private fun Endpoint.Request.emit(endpoint: Endpoint) = """
|${SPACER}class Request${content.emitContentType()} implements Request<${content?.reference?.emit() ?: "Void"}> {
|${SPACER}final class Request${content.emitContentType()} implements Request<${content?.reference?.emit() ?: "Void"}> {
|${SPACER}${SPACER}private final String path;
|${SPACER}${SPACER}private final Wirespec.Method method;
|${SPACER}${SPACER}private final java.util.Map<String, java.util.List<Object>> query;
Expand All @@ -157,8 +165,8 @@ class JavaEmitter(
""".trimMargin()

private fun Endpoint.Response.emit() = """
|${SPACER}class Response${status.firstToUpper()}${content.emitContentType()} implements Response${
status.takeIf { it.isInt() }?.groupStatus().orEmptyString()
|${SPACER}final class Response${status.firstToUpper()}${content.emitContentType()} implements Response${
status.firstToUpper().orEmptyString()
}<${content?.reference?.emit() ?: "Void"}> {
|${SPACER}${SPACER}private final int status;
|${SPACER}${SPACER}private final java.util.Map<String, java.util.List<Object>> headers;
Expand All @@ -177,7 +185,7 @@ class JavaEmitter(
""".trimMargin()

private fun List<Endpoint.Response>.emitResponseMapper() = """
|${SPACER}static <B> Response RESPONSE_MAPPER(Wirespec.ContentMapper<B> contentMapper, int status, java.util.Map<String, java.util.List<Object>> headers, Wirespec.Content<B> content) {
|${SPACER}static <B> Response RESPONSE_MAPPER(Wirespec.ContentMapper<B> contentMapper, Wirespec.Response<B> response) {
|${distinctBy { it.status to it.content?.type }.joinToString("") { it.emitResponseMapperCondition() }}
|${SPACER}${SPACER}throw new IllegalStateException("Unknown response type");
|${SPACER}}
Expand All @@ -187,21 +195,21 @@ class JavaEmitter(
when (content) {
null -> """
|${SPACER}${SPACER}${SPACER}if(${
status.takeIf { it.isInt() }?.let { "status == $status && " }.orEmptyString()
}content == null) { return new Response${status.firstToUpper()}Void(${
status.takeIf { !it.isInt() }?.let { "status, " }.orEmptyString()
}headers); }
status.takeIf { it.isInt() }?.let { "response.getStatus() == $status && " }.orEmptyString()
}response.getContent() == null) { return new Response${status.firstToUpper()}Void(${
status.takeIf { !it.isInt() }?.let { "response.getStatus(), " }.orEmptyString()
}response.getHeaders()); }
|
""".trimMargin()

else -> """
|${SPACER}${SPACER}${SPACER}if(${
status.takeIf { it.isInt() }?.let { "status == $status && " }.orEmptyString()
}content.type().equals("${content.type}")) {
|${SPACER}${SPACER}${SPACER}${SPACER}Wirespec.Content<${content.reference.emit()}> c = contentMapper.read(content, Wirespec.getType(${content.reference.emitPrimaryType()}.class, ${content.reference.isIterable}));
status.takeIf { it.isInt() }?.let { "response.getStatus() == $status && " }.orEmptyString()
}response.getContent().type().equals("${content.type}")) {
|${SPACER}${SPACER}${SPACER}${SPACER}Wirespec.Content<${content.reference.emit()}> content = contentMapper.read(response.getContent(), Wirespec.getType(${content.reference.emitPrimaryType()}.class, ${content.reference.isIterable}));
|${SPACER}${SPACER}${SPACER}${SPACER}return new Response${status.firstToUpper()}${content.emitContentType()}(${
status.takeIf { !it.isInt() }?.let { "status, " }.orEmptyString()
}headers, c.body());
status.takeIf { !it.isInt() }?.let { "response.getStatus(), " }.orEmptyString()
}response.getHeaders(), content.body());
|${SPACER}${SPACER}${SPACER}}
|
""".trimMargin()
Expand Down Expand Up @@ -231,11 +239,7 @@ class JavaEmitter(
.joinToString(", ") { it.emit() }
}

private fun List<Type.Shape.Field>.emitMap() = joinToString(
", ",
"java.util.Map.of(",
")"
) { "\"${it.identifier.emit()}\", java.util.List.of(${it.identifier.emit()})" }
private fun List<Type.Shape.Field>.emitMap() = joinToString(", ", "java.util.Map.of(", ")") { "\"${it.identifier.value}\", java.util.List.of(${it.identifier.emit()})" }

private fun List<Endpoint.Segment>.emitSegment() = "/" + joinToString("/") {
when (it) {
Expand All @@ -254,4 +258,21 @@ class JavaEmitter(
if (isInt()) substring(0, 1) + "XX"
else firstToUpper()

fun String.sanitizeKeywords() = if (reservedKeywords.contains(this)) "_$this" else this

fun String.sanitizeSymbols() = replace(".", "")
companion object {
private val reservedKeywords = listOf(
"abstract", "continue", "for", "new", "switch",
"assert", "default", "goto", "package", "synchronized",
"boolean", "do", "if", "private", "this",
"break", "double", "implements", "protected", "throw",
"byte", "else", "import", "public", "throws",
"case", "enum", "instanceof", "return", "transient",
"catch", "extends", "int", "short", "try",
"char", "final", "interface", "static", "void",
"class", "finally", "long", "strictfp", "volatile",
"const", "float", "native", "super", "while"
)
}
}
Loading

0 comments on commit 337223e

Please sign in to comment.