Skip to content

Commit

Permalink
Make query param object (#82)
Browse files Browse the repository at this point in the history
  • Loading branch information
wilmveel authored Jul 27, 2023
1 parent 17d9b4b commit edd7a4f
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 93 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ class JavaEmitter(
|public interface WirespecShared {
|${SPACER}enum Method { GET, PUT, POST, DELETE, OPTIONS, HEAD, PATCH, TRACE };
|${SPACER}record Content<T> (String type, T body) {};
|${SPACER}interface Request<T> { String getPath(); Method getMethod(); java.util.Map<String, java.util.List<String>> getQuery(); java.util.Map<String, java.util.List<String>> getHeaders(); Content<T> getContent(); }
|${SPACER}interface Response<T> { int getStatus(); java.util.Map<String, java.util.List<String>> getHeaders(); Content<T> getContent(); }
|${SPACER}interface Request<T> { String getPath(); Method getMethod(); java.util.Map<String, java.util.List<Object>> getQuery(); java.util.Map<String, java.util.List<Object>> getHeaders(); Content<T> getContent(); }
|${SPACER}interface Response<T> { int getStatus(); java.util.Map<String, java.util.List<Object>> getHeaders(); Content<T> getContent(); }
|${SPACER}interface ContentMapper<B> { <T> Content<T> read(Content<B> content, Type valueType); <T> Content<B> write(Content<T> content); }
|${SPACER}static Type getType(final Class<?> type, final boolean isIterable) {
|${SPACER}${SPACER}if(isIterable) {
Expand Down Expand Up @@ -115,8 +115,8 @@ class JavaEmitter(
|${SPACER}class Request${content.emitContentType()} implements Request<${content?.reference?.emit() ?: "Void"}> {
|${SPACER}${SPACER}private final String path;
|${SPACER}${SPACER}private final WirespecShared.Method method;
|${SPACER}${SPACER}private final java.util.Map<String, java.util.List<String>> query;
|${SPACER}${SPACER}private final java.util.Map<String, java.util.List<String>> headers;
|${SPACER}${SPACER}private final java.util.Map<String, java.util.List<Object>> query;
|${SPACER}${SPACER}private final java.util.Map<String, java.util.List<Object>> headers;
|${SPACER}${SPACER}private final WirespecShared.Content<${content?.reference?.emit() ?: "Void"}> content;
|${SPACER}${SPACER}public Request${content.emitContentType()}(${endpoint.emitRequestSignature(content)}) {
|${SPACER}${SPACER}${SPACER}this.path = ${endpoint.path.emitPath()};
Expand All @@ -127,30 +127,30 @@ class JavaEmitter(
|${SPACER}${SPACER}}
|${SPACER}${SPACER}@Override public String getPath() {return path;}
|${SPACER}${SPACER}@Override public WirespecShared.Method getMethod() {return method;}
|${SPACER}${SPACER}@Override public java.util.Map<String, java.util.List<String>> getQuery() {return query;}
|${SPACER}${SPACER}@Override public java.util.Map<String, java.util.List<String>> getHeaders() {return headers;}
|${SPACER}${SPACER}@Override public java.util.Map<String, java.util.List<Object>> getQuery() {return query;}
|${SPACER}${SPACER}@Override public java.util.Map<String, java.util.List<Object>> getHeaders() {return headers;}
|${SPACER}${SPACER}@Override public WirespecShared.Content<${content?.reference?.emit() ?: "Void"}> getContent() {return content;}
|${SPACER}}
""".trimMargin()

private fun Endpoint.Response.emit() = """
|${SPACER}class Response${status.firstToUpper()}${content.emitContentType()} implements Response${status.takeIf { it.isInt() }?.groupStatus().orEmptyString()}<${content?.reference?.emit() ?: "Void"}> {
|${SPACER}${SPACER}private final int status;
|${SPACER}${SPACER}private final java.util.Map<String, java.util.List<String>> headers;
|${SPACER}${SPACER}private final java.util.Map<String, java.util.List<Object>> headers;
|${SPACER}${SPACER}private final WirespecShared.Content<${content?.reference?.emit() ?: "Void"}> content;
|${SPACER}${SPACER}public Response${status.firstToUpper()}${content.emitContentType()}(${status.takeIf { !it.isInt() }?.let { "int status, " }.orEmptyString()}java.util.Map<String, java.util.List<String>> headers${content?.let { ", ${it.reference.emit()} body" } ?: ""}) {
|${SPACER}${SPACER}public Response${status.firstToUpper()}${content.emitContentType()}(${status.takeIf { !it.isInt() }?.let { "int status, " }.orEmptyString()}java.util.Map<String, java.util.List<Object>> headers${content?.let { ", ${it.reference.emit()} body" } ?: ""}) {
|${SPACER}${SPACER}${SPACER}this.status = ${status.takeIf { it.isInt() } ?: "status"};
|${SPACER}${SPACER}${SPACER}this.headers = headers;
|${SPACER}${SPACER}${SPACER}this.content = ${content?.let { "new WirespecShared.Content(\"${it.type}\", body)" } ?: "null"};
|${SPACER}${SPACER}}
|${SPACER}${SPACER}@Override public int getStatus() {return status;}
|${SPACER}${SPACER}@Override public java.util.Map<String, java.util.List<String>> getHeaders() {return headers;}
|${SPACER}${SPACER}@Override public java.util.Map<String, java.util.List<Object>> getHeaders() {return headers;}
|${SPACER}${SPACER}@Override public WirespecShared.Content<${content?.reference?.emit() ?: "Void"}> getContent() {return content;}
|${SPACER}}
""".trimMargin()

private fun List<Endpoint.Response>.emitResponseMapper() = """
|${SPACER}static <B> Response RESPONSE_MAPPER(WirespecShared.ContentMapper<B> contentMapper, int status, java.util.Map<String, java.util.List<String>> headers, WirespecShared.Content<B> content) {
|${SPACER}static <B> Response RESPONSE_MAPPER(WirespecShared.ContentMapper<B> contentMapper, int status, java.util.Map<String, java.util.List<Object>> headers, WirespecShared.Content<B> content) {
|${joinToString (""){ it.emitResponseMapperCondition() }}
|${SPACER}${SPACER}throw new IllegalStateException("Unknown response type");
|${SPACER}}
Expand Down Expand Up @@ -195,7 +195,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()}.toString())" }
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<Endpoint.Segment>.emitSegment() = "/" + joinToString("/") {
when (it) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ class KotlinEmitter(
|object WirespecShared {
|${SPACER}enum class Method { GET, PUT, POST, DELETE, OPTIONS, HEAD, PATCH, TRACE }
|${SPACER}data class Content<T> (val type:String, val body:T )
|${SPACER}interface Request<T> { val path:String; val method: Method; val query: Map<String, List<String>>; val headers: Map<String, List<String>>; val content:Content<T>? }
|${SPACER}interface Response<T> { val status:Int; val headers: Map<String, List<String>>; val content:Content<T>? }
|${SPACER}interface Request<T> { val path:String; val method: Method; val query: Map<String, List<Any?>>; val headers: Map<String, List<Any?>>; val content:Content<T>? }
|${SPACER}interface Response<T> { val status:Int; val headers: Map<String, List<Any?>>; val content:Content<T>? }
|${SPACER}interface ContentMapper<B> { fun <T> read(content: Content<B>, valueType: KType): Content<T> fun <T> write(content: Content<T>): Content<B> }
|}
""".trimMargin()
Expand Down Expand Up @@ -90,7 +90,7 @@ class KotlinEmitter(
emitRequestSignature(
it.content
)
}: Request<${it.content?.reference?.emit() ?: "Unit"}> {override val path = \"${path.emitPath()}\"; override val method = WirespecShared.Method.${method.name}; override val query = mapOf<String, List<String>>(${query.emitMap()}); override val headers = mapOf<String, List<String>>(${headers.emitMap()}); override val content = ${it.content?.let { "WirespecShared.Content(\"${it.type}\", body)" } ?: "null"}}"
}: Request<${it.content?.reference?.emit() ?: "Unit"}> {override val path = \"${path.emitPath()}\"; override val method = WirespecShared.Method.${method.name}; override val query = mapOf<String, List<Any?>>(${query.emitMap()}); override val headers = mapOf<String, List<Any?>>(${headers.emitMap()}); override val content = ${it.content?.let { "WirespecShared.Content(\"${it.type}\", body)" } ?: "null"}}"
}
}
|${SPACER}sealed interface Response<T>: WirespecShared.Response<T>
Expand All @@ -104,11 +104,11 @@ class KotlinEmitter(
}
|${
responses.filter { it.status.isInt() }
.joinToString("\n") { "${SPACER}class Response${it.status}${it.content?.emitContentType() ?: "Unit"} (override val headers: Map<String, List<String>>${it.content?.let { ", body: ${it.reference.emit()}" } ?: ""} ): Response${it.status}<${it.content?.reference?.emit() ?: "Unit"}> { override val status = ${it.status}; override val content = ${it.content?.let { "WirespecShared.Content(\"${it.type}\", body)" } ?: "null"}}" }
.joinToString("\n") { "${SPACER}class Response${it.status}${it.content?.emitContentType() ?: "Unit"} (override val headers: Map<String, List<Any?>>${it.content?.let { ", body: ${it.reference.emit()}" } ?: ""} ): Response${it.status}<${it.content?.reference?.emit() ?: "Unit"}> { override val status = ${it.status}; override val content = ${it.content?.let { "WirespecShared.Content(\"${it.type}\", body)" } ?: "null"}}" }
}
|${
responses.filter { !it.status.isInt() }
.joinToString("\n") { "${SPACER}class Response${it.status.firstToUpper()}${it.content?.emitContentType() ?: "Unit"} (override val status: Int, override val headers: Map<String, List<String>>${it.content?.let { ", body: ${it.reference.emit()}" } ?: ""} ): Response${it.status.firstToUpper()}<${it.content?.reference?.emit() ?: "Unit"}> { override val content = ${it.content?.let { "WirespecShared.Content(\"${it.type}\", body)" } ?: "null"}}" }
.joinToString("\n") { "${SPACER}class Response${it.status.firstToUpper()}${it.content?.emitContentType() ?: "Unit"} (override val status: Int, override val headers: Map<String, List<Any?>>${it.content?.let { ", body: ${it.reference.emit()}" } ?: ""} ): Response${it.status.firstToUpper()}<${it.content?.reference?.emit() ?: "Unit"}> { override val content = ${it.content?.let { "WirespecShared.Content(\"${it.type}\", body)" } ?: "null"}}" }
}
|suspend fun ${name.firstToLower()}(request: Request<*>): Response<*>
|${SPACER}companion object{
Expand Down Expand Up @@ -143,7 +143,7 @@ class KotlinEmitter(
}

private fun List<Type.Shape.Field>.emitMap() =
joinToString(", ") { "\"${it.identifier.emit()}\" to listOf(${it.identifier.emit()}.toString())" }
joinToString(", ") { "\"${it.identifier.emit()}\" to listOf(${it.identifier.emit()})" }

private fun Endpoint.Segment.emit(): String = withLogging(logger) {
when (this) {
Expand All @@ -163,7 +163,7 @@ class KotlinEmitter(

private fun List<Endpoint.Response>.emitResponseMapper() = """
|fun <B> RESPONSE_MAPPER(contentMapper: WirespecShared.ContentMapper<B>) =
|${SPACER}fun(status: Int, headers:Map<String, List<String>>, content: WirespecShared.Content<B>?) =
|${SPACER}fun(status: Int, headers:Map<String, List<Any?>>, content: WirespecShared.Content<B>?) =
|${SPACER}${SPACER}when {
|${filter { it.status.isInt() }.joinToString("\n") { it.emitResponseMapperCondition() }}
|${filter { !it.status.isInt() }.joinToString("\n") { it.emitResponseMapperCondition() }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,13 @@ object OpenApiParser {
.let { it + method.name }
val query = parameters
.filter { it.`in` == ParameterLocation.QUERY }
.mapNotNull { it.toField(openApi) }
.map { it.toField() }
val headers = parameters
.filter { it.`in` == ParameterLocation.HEADER }
.mapNotNull { it.toField(openApi) }
.map { it.toField() }
val cookies = parameters
.filter { it.`in` == ParameterLocation.COOKIE }
.mapNotNull { it.toField(openApi) }
.map { it.toField() }
val requests = operation?.requestBody?.resolve(openApi)
?.let { requestBody ->
requestBody.content
Expand Down Expand Up @@ -124,7 +124,7 @@ object OpenApiParser {

val componentsAst = openApi.components?.schemas
?.flatMap { it.value.flatten(Common.className(it.key), openApi) }
?.map { Type(it.name, Type.Shape(it.fields())) }
?.map { Type(it.name, Type.Shape(it.properties.map { it.field })) }
?: emptyList()

return endpointAst + componentsAst
Expand Down Expand Up @@ -229,17 +229,54 @@ private fun SchemaObject.flatten(
properties = properties
?.map { (key, value) ->
when (value) {
is SchemaObject -> SimpleProp(
is SchemaObject -> {
val reference = when (value.type) {
OpenapiType.STRING, OpenapiType.NUMBER, OpenapiType.INTEGER, OpenapiType.BOOLEAN -> Primitive(
(value.type as OpenapiType).toPrimitive(),
false
)

OpenapiType.ARRAY -> {
val resolve = value.items?.resolve(openApi)
when (resolve?.type) {
OpenapiType.STRING, OpenapiType.NUMBER, OpenapiType.INTEGER, OpenapiType.BOOLEAN -> Primitive(
(resolve.type as OpenapiType).toPrimitive(),
true
)

else -> when (value.items) {
is ReferenceObject -> Reference.Custom(
(value.items as ReferenceObject).getReference(),
true
)

else -> TODO()
}
}

}

OpenapiType.OBJECT -> Reference.Custom(Common.className(name, key), false)
null -> TODO()
}
SimpleProp(
key = key,
field = Field(
Field.Identifier(key),
reference,
!(this.required?.contains(key) ?: false)
)
)
}

is ReferenceObject -> SimpleProp(
key = key,
type = value.type,
field = Field(
Field.Identifier(key),
Reference.Custom(Common.className(name, key), false),
false
Reference.Custom(value.getReference(), false),
!(this.required?.contains(key) ?: false)
)
)

is ReferenceObject -> TODO()
}
} ?: emptyList()
)
Expand Down Expand Up @@ -339,55 +376,27 @@ private fun OpenapiType.toPrimitive() = when (this) {
else -> error("Type is not a primitive")
}

private fun ParameterObject.toField(openApi: OpenAPIObject) = schema
?.resolve(openApi)
?.type
?.toPrimitive()
?.let { Field(Field.Identifier(name), Primitive(it, false), this.required ?: false) }


private data class SimpleSchema(val name: String, val properties: List<SimpleProp>)
private data class SimpleProp(val key: String, val type: OpenapiType?, val field: Field)

private fun SimpleSchema.fields() = properties
.map {
when (it.type) {
OpenapiType.STRING -> Field(
Field.Identifier(it.key),
Field.Reference.Primitive(
Field.Reference.Primitive.Type.String,
private fun ParameterObject.toField() =
when (schema) {
is ReferenceObject -> Reference.Custom((schema as ReferenceObject).getReference(), false)
is SchemaObject -> {
when (val type = (schema as SchemaObject).type) {
OpenapiType.STRING, OpenapiType.INTEGER, OpenapiType.NUMBER, OpenapiType.BOOLEAN -> Primitive(
type.toPrimitive(),
false
),
false
)
)

OpenapiType.NUMBER -> Field(
Field.Identifier(it.key),
Field.Reference.Primitive(
Field.Reference.Primitive.Type.Integer,
false
),
false
)
OpenapiType.ARRAY -> TODO()
OpenapiType.OBJECT -> TODO()
null -> TODO()
}
}

OpenapiType.INTEGER -> Field(
Field.Identifier(it.key),
Field.Reference.Primitive(
Field.Reference.Primitive.Type.Integer,
false
),
false
)
null -> TODO()
}
.let { Field(Field.Identifier(name), it, this.required ?: false) }

OpenapiType.BOOLEAN -> Field(
Field.Identifier(it.key),
Field.Reference.Primitive(
Field.Reference.Primitive.Type.Boolean,
false
),
false
)

else -> it.field
}
}
private data class SimpleSchema(val name: String, val properties: List<SimpleProp>)
private data class SimpleProp(val key: String, val field: Field)

Loading

0 comments on commit edd7a4f

Please sign in to comment.