This is a feature of the code that is processing t...
# http4k
j
This is a feature of the code that is processing the request body. Assuming you are using json and Jackson, you can do this by making the relevant field not-nullable, or by setting the relevant options in jackson.
👍 1
e
My field is defined as not nullable but still in the OpenAPI Spec generated, the component object comes back with
required
attribute as
[]
. I tried setting
@JsonProperty(required = true)
on the field as well, but still no luck. Would you have other thoughts?
@dave or @James Richardson
d
The annotation won't affect the ooenapi
If the dto is Kotlin and you're using Jackson it should just work
the only way we can help beyond that is for you to create a Gist or similar which recreates the problem.
j
Sorry I can't help with the openapi stuff, I dont really use it.
e
@dave Here is a slimmed down version of my issue using the example given in https://www.http4k.org/cookbook/typesafe_http_contracts/
Copy code
data class SumResult(val sum: Int)
val sumResponseBody = Body.auto<SumResult>().toLens()

fun main(args: Array<String>) {
    fun add(value1: Int, value2: Int): HttpHandler = {
        Response(OK).with(
            sumResponseBody of SumResult(value1 + value2)
        )
    }

    val filter: Filter = ResponseFilters.ReportHttpTransaction(Clock.systemUTC()) { tx: HttpTransaction ->
        println(tx.labels.toString() + " took " + tx.duration)
    }

    val mySecurity = ApiKeySecurity(<http://Query.int|Query.int>().required("apiKey"), { it == 42 })

    val contract = contract {
        renderer = OpenApi3(ApiInfo("my great api", "v1.0"), OpenApiJackson, emptyList(), OpenApi3ApiRenderer(OpenApiJackson))
        descriptionPath = "/docs/swagger.json"
        security = mySecurity

        routes += "/add" / <http://Path.int|Path.int>().of("value1") / <http://Path.int|Path.int>().of("value2") meta {
            summary = "add"
            description = "Adds 2 numbers together"
            returning(OK, sumResponseBody to SumResult(55))
        } bindContract Method.GET to ::add
    }

    val handler = routes(
        "/context" bind filter.then(contract),
        "/" bind contract {
            renderer = OpenApi3(ApiInfo("my great super api", "v1.0"), OpenApiJackson, emptyList(), OpenApi3ApiRenderer(OpenApiJackson))
        }
    )

    ServerFilters.Cors(CorsPolicy.UnsafeGlobalPermissive).then(handler).asServer(Jetty(8000)).start()
}
When I run
localhost:8080/context/docs/swagger.json
on my browser or curl command, the output I get is this
Copy code
{
  "openapi": "3.0.0",
  "info": {
    "title": "my great api",
    "version": "v1.0",
    "description": ""
  },
  "tags": [],
  "paths": {
    "/context/add/{value1}/{value2}": {
      "get": {
        "summary": "add",
        "description": "Adds 2 numbers together",
        "tags": [
          "/context"
        ],
        "parameters": [
          {
            "schema": {
              "type": "integer"
            },
            "in": "path",
            "name": "value1",
            "required": true,
            "description": null
          },
          {
            "schema": {
              "type": "integer"
            },
            "in": "path",
            "name": "value2",
            "required": true,
            "description": null
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "example": {
                  "sum": 55
                },
                "schema": {
                  "$ref": "#/components/schemas/object-1725201476"
                }
              }
            }
          }
        },
        "security": [
          {
            "api_key": []
          }
        ],
        "operationId": "getContextAdd_value1__value2",
        "deprecated": false
      }
    }
  },
  "components": {
    "schemas": {
      "object-1725201476": {
        "type": "object",
        "required": [],
        "properties": {
          "sum": {
            "type": "integer",
            "example": 55
          }
        }
      }
    },
    "securitySchemes": {
      "api_key": {
        "type": "apiKey",
        "in": "query",
        "name": "apiKey"
      }
    }
  }
}
If you notice, under the
components.schemas.object-1725201476
,
required
comes back as
[]
even though my data class
SumResult
has
sum
property as not nullable.
My object mappers are defined as below
Copy code
object ObjectMappers {
    val snakeCaseMapper: ObjectMapper get() = createMapper()

    fun createMapper(namingStrategy: PropertyNamingStrategy = PropertyNamingStrategy.SNAKE_CASE): ObjectMapper =
        ObjectMapper()
            .findAndRegisterModules()
            .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
            .configure(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE, false)
            .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
            .configure(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL, true)
            .configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true)
            .configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true)
            .setPropertyNamingStrategy(namingStrategy)
}

object OpenApiJackson : ConfigurableJackson(ObjectMappers.snakeCaseMapper)
Am I missing anything here? How can I get the
required
field to be filled with non-nullable properties of my response object in my example
d
you need the Kotlin module at least in order to pick up the nullability
you should base your mapper from this:
Copy code
object Jackson : ConfigurableJackson(KotlinModule()
    .asConfigurable()
    .withStandardMappings()
    .done()
    .deactivateDefaultTyping()
    .configure(FAIL_ON_UNKNOWN_PROPERTIES, false)
    .configure(FAIL_ON_IGNORED_PROPERTIES, false)
    .configure(USE_BIG_DECIMAL_FOR_FLOATS, true)
    .configure(USE_BIG_INTEGER_FOR_INTS, true)
)
e
Okay, let me try with this.
I used the mapper from your snippet but still same response.
required
comes back as
[]
in the component schema objects
d
did you replace the Body.auto import?
e
Yes.. But still same response..
d
you're using OpenApiJackson to render the output
how is that defined?
it's that instance which will determine the null/nonnull
you should use the same Jackson for both things - not doing so will lead to bad times and sad faces
e
I am using the same jackson everywhere.. Here it is
Copy code
object Jackson : ConfigurableJackson(KotlinModule()
    .asConfigurable()
    .withStandardMappings()
    .done()
    .deactivateDefaultTyping()
    .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
    .configure(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES, false)
    .configure(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS, true)
    .configure(DeserializationFeature.USE_BIG_INTEGER_FOR_INTS, true)
)
Copy code
package com.test.openapi

import com.test.openapi.Jackson.auto
import org.http4k.contract.contract
import org.http4k.contract.div
import org.http4k.contract.meta
import org.http4k.contract.openapi.ApiInfo
import org.http4k.contract.openapi.v3.OpenApi3
import org.http4k.contract.openapi.v3.OpenApi3ApiRenderer
import org.http4k.contract.security.ApiKeySecurity
import org.http4k.core.Body
import org.http4k.core.Filter
import org.http4k.core.HttpHandler
import org.http4k.core.HttpTransaction
import org.http4k.core.Method
import org.http4k.core.Response
import org.http4k.core.Status.Companion.OK
import org.http4k.core.then
import org.http4k.core.with
import org.http4k.filter.CorsPolicy
import org.http4k.filter.ResponseFilters
import org.http4k.filter.ServerFilters
import org.http4k.lens.Path
import org.http4k.lens.Query
import <http://org.http4k.lens.int|org.http4k.lens.int>
import org.http4k.routing.bind
import org.http4k.routing.routes
import org.http4k.server.Jetty
import org.http4k.server.asServer
import java.time.Clock

class App {
    val greeting: String
        get() {
            return "Hello world."
        }
}

data class SumResult(val sum: Int)
val sumResponseBody = Body.auto<SumResult>().toLens()

fun main(args: Array<String>) {
    fun add(value1: Int, value2: Int): HttpHandler = {
        Response(OK).with(
            sumResponseBody of SumResult(value1 + value2)
        )
    }

    val filter: Filter = ResponseFilters.ReportHttpTransaction(Clock.systemUTC()) { tx: HttpTransaction ->
        println(tx.labels.toString() + " took " + tx.duration)
    }

    val mySecurity = ApiKeySecurity(<http://Query.int|Query.int>().required("apiKey"), { it == 42 })

    val contract = contract {
        renderer = OpenApi3(ApiInfo("my great api", "v1.0"), Jackson, emptyList(), OpenApi3ApiRenderer(Jackson))
        descriptionPath = "/docs/swagger.json"
        security = mySecurity

        routes += "/add" / <http://Path.int|Path.int>().of("value1") / <http://Path.int|Path.int>().of("value2") meta {
            summary = "add"
            description = "Adds 2 numbers together"
            returning(OK, sumResponseBody to SumResult(55))
        } bindContract Method.GET to ::add
    }

    val handler = routes(
        "/context" bind filter.then(contract),
        "/" bind contract {
            renderer = OpenApi3(ApiInfo("my great super api", "v1.0"), Jackson, emptyList(), OpenApi3ApiRenderer(Jackson))
        }
    )

    ServerFilters.Cors(CorsPolicy.UnsafeGlobalPermissive).then(handler).asServer(Jetty(8000)).start()
d
renderer = OpenApi3(ApiInfo("my great api", "v1.0"), OpenApiJackson, emptyList(), OpenApi3ApiRenderer(OpenApiJackson))
e
Even after changing the OpenApiJackson to Jackson everywhere, still no
required
in the components schemas object
d
unfortunately the tests don't agree. 🙂
but will take a look
e
Can I take a look at the test which asserts for the presence of
required
attribute in the component schemas?
d
ooh - that's interesting... just checking something..
but the object makeup looks odd
"object-1725201476":
indicates that the renderer thinks that the backing object is JSON and not a Kotlin object
otherwise you'd get names like:
"#/customPrefix/org.http4k.contract.openapi.v3.ArbObject2"
👍 1
(from the class instead of randomly generated)
e
Sure, makes sense. So how do we go about fixing it? Anything wrong with what I am doing in my sample code?
d
I need to dig in a bit to see what is going on
👍 1
yeah - the code is definitely working. must be something with the example. A fuller version of the passing schema (full OA3) is here: https://github.com/http4k/http4k/blob/3bc44dfeb5f16d6a2b4ab8f5cc01e9b609f3e64a/htt[…]ct/openapi/v3/OpenApi3AutoTest.renders%20as%20expected.approved
e
Or is it the difference of Argo vs JSON modules?
d
argo won't do it
e
The example uses Argo whereas I used JSON in my sample code
d
you need JAckson for it to be fully working
e
So when I removed the OpenApi3Renderer from the OpenApi3 call, ie..
Copy code
val contract = contract {
        renderer = OpenApi3(ApiInfo("my great api", "v1.0"), Jackson, emptyList())//, OpenApi3ApiRenderer(Jackson))
        descriptionPath = "/docs/swagger.json"
        security = mySecurity

        routes += "/add" / <http://Path.int|Path.int>().of("value1") / <http://Path.int|Path.int>().of("value2") meta {
            summary = "add"
            description = "Adds 2 numbers together"
            returning(OK, sumResponseBody to SumResult(55))
        } bindContract Method.GET to ::add
    }
I get the
required
properties
d
ah yes - it's that
I was jsut coming to the same conclusion!
the example is a bit out of date by the looks of it.
e
why would it behave differently with OpenApi3ApiRenderer?
Is OpenApi3ApiRenderer not needed?
d
it's used for json libraries that don't do the automarshalling of the schema
(basically anything that isn't Jackson)
you want to call this method:
Copy code
fun OpenApi3(apiInfo: ApiInfo, json: Jackson = Jackson, extensions: List<OpenApiExtension> = emptyList()) = OpenApi3(apiInfo, json, extensions, ApiRenderer.Auto(json, AutoJsonToJsonSchema(json)))
and hence use this: ApiRenderer.Auto(json, AutoJsonToJsonSchema(json))
I'll update the example
sorry about the comfusion
e
No problem, looking forward to see the updated example.
Thanks @dave for helping to figure this out!
d
No problem. Thanks for bearing with us! 🙂
the example is actually ok - it was just a mistake to import the non-auto renderer. I'll try to add some docs to make sure others don't fall into the same trap 🙂
e
What do you mean by
non-auto renderer
?
d
OpenApi3ApiRenderer(Jackson)
is the bog standard renderer which doesn't support required fields. The one that does support it comes from
ApiRenderer.Auto()
- that's the default that is used in the example if you click through (there are 2 ways to make an
OpenApi3()
) - one in jacksonExt.kt and one in the class itself
you tripped up when you wanted to add the extensions. 🙂
e
Copy code
HTTP ERROR 500 org.http4k.contract.openapi.v3.NoFieldFound: Could not find sum_value in SumResult(sumValue=55)
URI:	/context/docs/swagger.json
STATUS:	500
MESSAGE:	org.http4k.contract.openapi.v3.NoFieldFound: Could not find sum_value in SumResult(sumValue=55)
SERVLET:	org.http4k.servlet.HttpHandlerServlet-438bad7c
CAUSED BY:	org.http4k.contract.openapi.v3.NoFieldFound: Could not find sum_value in SumResult(sumValue=55)
Caused by:

org.http4k.contract.openapi.v3.NoFieldFound: Could not find sum_value in SumResult(sumValue=55)
	at org.http4k.contract.openapi.v3.FieldRetrieval$Companion$compose$1.invoke(FieldRetrieval.kt:17)
	at org.http4k.contract.openapi.v3.FieldRetrieval$Companion$compose$1.invoke(FieldRetrieval.kt:10)
	at org.http4k.contract.openapi.v3.AutoJsonToJsonSchema.toObjectSchema(AutoJsonToJsonSchema.kt:94)
	at org.http4k.contract.openapi.v3.AutoJsonToJsonSchema.toObjectOrMapSchema(AutoJsonToJsonSchema.kt:78)
	at org.http4k.contract.openapi.v3.AutoJsonToJsonSchema.toSchema(AutoJsonToJsonSchema.kt:35)
	at org.http4k.contract.openapi.v3.AutoJsonToJsonSchema.toSchema(AutoJsonToJsonSchema.kt:25)
	at org.http4k.contract.openapi.ApiRenderer$Companion$Auto$fallbackSchema$1.toSchema(ApiRenderer.kt:30)
	at org.http4k.contract.openapi.ApiRenderer$Companion$Auto$1.toSchema(ApiRenderer.kt)
	at org.http4k.contract.openapi.v3.OpenApi3.toSchemaContent(OpenApi3.kt:202)
	at org.http4k.contract.openapi.v3.OpenApi3.collectSchemas(OpenApi3.kt:140)
	at org.http4k.contract.openapi.v3.OpenApi3.responses(OpenApi3.kt:132)
	at org.http4k.contract.openapi.v3.OpenApi3.apiPath(OpenApi3.kt:106)
	at org.http4k.contract.openapi.v3.OpenApi3.asPath(OpenApi3.kt:92)
	at org.http4k.contract.openapi.v3.OpenApi3.description(OpenApi3.kt:68)
	at org.http4k.contract.ContractRoutingHttpHandler$$special$$inlined$let$lambda$1.invoke(ContractRoutingHttpHandler.kt:66)
	at org.http4k.contract.ContractRoutingHttpHandler$$special$$inlined$let$lambda$1.invoke(ContractRoutingHttpHandler.kt:26)
	at org.http4k.routing.RouterMatch$MatchingHandler.invoke(Router.kt:47)
	at org.http4k.routing.RouterMatch$MatchingHandler.invoke(Router.kt:46)
	at org.http4k.core.Http4kKt$NoOp$1$1.invoke(Http4k.kt:11)
	at org.http4k.core.Http4kKt$NoOp$1$1.invoke(Http4k.kt)
	at org.http4k.core.Http4kKt$NoOp$1$1.invoke(Http4k.kt:11)
	at org.http4k.core.Http4kKt$NoOp$1$1.invoke(Http4k.kt)
	at org.http4k.core.Http4kKt$NoOp$1$1.invoke(Http4k.kt:11)
	at org.http4k.core.Http4kKt$NoOp$1$1.invoke(Http4k.kt)
	at org.http4k.filter.ResponseFilters$ReportHttpTransaction$invoke$2$1.invoke(ResponseFilters.kt:48)
	at org.http4k.filter.ResponseFilters$ReportHttpTransaction$invoke$2$1.invoke(ResponseFilters.kt:40)
	at org.http4k.filter.ServerFilters$Cors$invoke$1$1.invoke(ServerFilters.kt:55)
	at org.http4k.filter.ServerFilters$Cors$invoke$1$1.invoke(ServerFilters.kt:50)
	at org.http4k.contract.ContractRoutingHttpHandler$identify$1$1$1.invoke(ContractRoutingHttpHandler.kt:105)
	at org.http4k.contract.ContractRoutingHttpHandler$identify$1$1$1.invoke(ContractRoutingHttpHandler.kt:26)
	at org.http4k.core.Http4kKt$then$2.invoke(Http4k.kt:15)
	at org.http4k.core.Http4kKt$then$2.invoke(Http4k.kt)
	at org.http4k.routing.RouterMatch$MatchingHandler.invoke(Router.kt:47)
	at org.http4k.routing.RouterMatch$MatchingHandler.invoke(Router.kt:46)
	at org.http4k.routing.RouterBasedHttpHandler.invoke(RouterBasedHttpHandler.kt:23)
	at org.http4k.routing.RouterBasedHttpHandler.invoke(RouterBasedHttpHandler.kt:13)
	at org.http4k.servlet.Http4kServletAdapter.handle(Http4kServletAdapter.kt:19)
	at org.http4k.servlet.HttpHandlerServlet.service(HttpHandlerServlet.kt:14)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:750)
	at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:761)
	at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:517)
	at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:226)
	at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:1576)
	at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:226)
	at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1358)
	at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:181)
	at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:472)
	at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:1549)
	at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:179)
	at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1282)
	at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:134)
	at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:127)
	at org.eclipse.jetty.server.Server.handle(Server.java:567)
	at org.eclipse.jetty.server.HttpChannel.lambda$handle$0(HttpChannel.java:404)
	at org.eclipse.jetty.server.HttpChannel.dispatch(HttpChannel.java:661)
	at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:396)
	at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:289)
	at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:324)
	at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:105)
	at org.eclipse.jetty.io.SocketChannelEndPoint$1.run(SocketChannelEndPoint.java:106)
	at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:790)
	at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:912)
	at java.base/java.lang.Thread.run(Thread.java:834)
Qq - on removing the OpenApi3ApiRenderer, my openapi spec breaks left, right and center in my actual code 🙂 To ascertain this, I modified the sample gist to this
data class SumResult(val sumValue: Int)
from
data class SumResult(val sum: Int)
. I get the following error when I navigate to
<http://localhost:8000/context/docs/swagger.json>
Definitely something with Jackson wiring and its property naming strategy?
@dave Is there any reason you know of why the
json
in
OpenApi3
call expects the Kotlin data class member variables to be in snake_case?
d
"expects"? can you elaborate?
e
So when I have my Jackson defined like this
Copy code
object Jackson : ConfigurableJackson(
    ObjectMapper()
        .registerModule(KotlinModule())
        .setDefaultPropertyInclusion(JsonInclude.Include.NON_NULL)
        .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
        .configure(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE, false)
        .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
        .configure(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL, true)
        .configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true)
        .configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true)
        .setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE))
and my data class and corresponding lens defined like this
Copy code
data class SumResult(val sumValue: Int)

val sumResponseBody = Body.auto<SumResult>().toLens()
I run into an error when I get the openapi specs
Copy code
org.http4k.contract.openapi.v3.NoFieldFound: Could not find sum_value in SumResult(sumValue=55)
d
what's the stack on that?
e
sum_value
is what is being looked for.. not
sumValue
from my data class definition
Copy code
org.http4k.contract.openapi.v3.NoFieldFound: Could not find sum_value in SumResult(sumValue=55)
	at org.http4k.contract.openapi.v3.FieldRetrieval$Companion$compose$1.invoke(FieldRetrieval.kt:17)
	at org.http4k.contract.openapi.v3.FieldRetrieval$Companion$compose$1.invoke(FieldRetrieval.kt:10)
	at org.http4k.contract.openapi.v3.AutoJsonToJsonSchema.toObjectSchema(AutoJsonToJsonSchema.kt:94)
	at org.http4k.contract.openapi.v3.AutoJsonToJsonSchema.toObjectOrMapSchema(AutoJsonToJsonSchema.kt:78)
	at org.http4k.contract.openapi.v3.AutoJsonToJsonSchema.toSchema(AutoJsonToJsonSchema.kt:35)
	at org.http4k.contract.openapi.v3.AutoJsonToJsonSchema.toSchema(AutoJsonToJsonSchema.kt:25)
	at org.http4k.contract.openapi.ApiRenderer$Companion$Auto$fallbackSchema$1.toSchema(ApiRenderer.kt:30)
	at org.http4k.contract.openapi.ApiRenderer$Companion$Auto$1.toSchema(ApiRenderer.kt)
	at org.http4k.contract.openapi.v3.OpenApi3.toSchemaContent(OpenApi3.kt:202)
	at org.http4k.contract.openapi.v3.OpenApi3.collectSchemas(OpenApi3.kt:140)
	at org.http4k.contract.openapi.v3.OpenApi3.responses(OpenApi3.kt:132)
	at org.http4k.contract.openapi.v3.OpenApi3.apiPath(OpenApi3.kt:106)
	at org.http4k.contract.openapi.v3.OpenApi3.asPath(OpenApi3.kt:92)
	at org.http4k.contract.openapi.v3.OpenApi3.description(OpenApi3.kt:68)
	at org.http4k.contract.ContractRoutingHttpHandler$$special$$inlined$let$lambda$1.invoke(ContractRoutingHttpHandler.kt:66)
	at org.http4k.contract.ContractRoutingHttpHandler$$special$$inlined$let$lambda$1.invoke(ContractRoutingHttpHandler.kt:26)
	at org.http4k.routing.RouterMatch$MatchingHandler.invoke(Router.kt:47)
	at org.http4k.routing.RouterMatch$MatchingHandler.invoke(Router.kt:46)
	at org.http4k.core.Http4kKt$NoOp$1$1.invoke(Http4k.kt:11)
	at org.http4k.core.Http4kKt$NoOp$1$1.invoke(Http4k.kt)
	at org.http4k.core.Http4kKt$NoOp$1$1.invoke(Http4k.kt:11)
	at org.http4k.core.Http4kKt$NoOp$1$1.invoke(Http4k.kt)
	at org.http4k.core.Http4kKt$NoOp$1$1.invoke(Http4k.kt:11)
	at org.http4k.core.Http4kKt$NoOp$1$1.invoke(Http4k.kt)
	at org.http4k.filter.ResponseFilters$ReportHttpTransaction$invoke$2$1.invoke(ResponseFilters.kt:48)
	at org.http4k.filter.ResponseFilters$ReportHttpTransaction$invoke$2$1.invoke(ResponseFilters.kt:40)
	at org.http4k.filter.ServerFilters$Cors$invoke$1$1.invoke(ServerFilters.kt:55)
	at org.http4k.filter.ServerFilters$Cors$invoke$1$1.invoke(ServerFilters.kt:50)
	at org.http4k.contract.ContractRoutingHttpHandler$identify$1$1$1.invoke(ContractRoutingHttpHandler.kt:105)
	at org.http4k.contract.ContractRoutingHttpHandler$identify$1$1$1.invoke(ContractRoutingHttpHandler.kt:26)
	at org.http4k.core.Http4kKt$then$2.invoke(Http4k.kt:15)
	at org.http4k.core.Http4kKt$then$2.invoke(Http4k.kt)
	at org.http4k.routing.RouterMatch$MatchingHandler.invoke(Router.kt:47)
	at org.http4k.routing.RouterMatch$MatchingHandler.invoke(Router.kt:46)
	at org.http4k.routing.RouterBasedHttpHandler.invoke(RouterBasedHttpHandler.kt:23)
	at org.http4k.routing.RouterBasedHttpHandler.invoke(RouterBasedHttpHandler.kt:13)
	at org.http4k.servlet.Http4kServletAdapter.handle(Http4kServletAdapter.kt:19)
	at org.http4k.servlet.HttpHandlerServlet.service(HttpHandlerServlet.kt:14)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:750)
	at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:761)
	at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:517)
	at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:226)
	at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:1576)
	at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:226)
	at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1358)
	at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:181)
	at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:472)
	at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:1549)
	at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:179)
	at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1282)
	at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:134)
	at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:127)
	at org.eclipse.jetty.server.Server.handle(Server.java:567)
	at org.eclipse.jetty.server.HttpChannel.lambda$handle$0(HttpChannel.java:404)
	at org.eclipse.jetty.server.HttpChannel.dispatch(HttpChannel.java:661)
	at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:396)
	at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:289)
	at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:324)
	at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:105)
	at org.eclipse.jetty.io.SocketChannelEndPoint$1.run(SocketChannelEndPoint.java:106)
	at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:790)
	at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:912)
	at java.base/java.lang.Thread.run(Thread.java:834)
d
There is a renamingStrategy configued in SimpleLookup - you might have luck in using that instead
but to make your life easier, I really would suggest trying to stick to camelCase naming
e
When I make my Jackson to have
PropertyNamingStrategy
as
LOWER_CAMEL_CASE
instead of
SNAKE_CASE
, the openApi spec generates correctly
d
yeah - there is a concept called "FieldRetrieval" which attempts to lookup the field on the object when creating the metadata (which is needed for all the required etc..). The SimpleLookup is one of these objects
e
My understanding of Jackson is very limited.. so please bare with me.. If I set the naming strategy to be camelCase, would it alter the way my JSON files are being read? Because in my actual project, the actual JSON files come with attributes in snake_case and data classes are defined with camelCase
d
yeah - then you probably want to setup a custom SimpleLookup when creating your renderer, or use the Jackson property annotations to convert them
e
Is there an example of how to use the OpenApi3ApiRenderer (if I am understanding you correctly) with custom SimpleLookup?
d
no.
yo'll have to play with it
e
But from our earlier conversation, you suggested to not use the OpenApi3Renderer, correct which doesn’t generate the
required
meta data on the component schemas?
d
you don't need to use the OpenApi3Renderer
Copy code
fun OpenApi3(apiInfo: ApiInfo, json: Jackson = Jackson, extensions: List<OpenApiExtension> = emptyList()) = OpenApi3(apiInfo, json, extensions, ApiRenderer.Auto(json, AutoJsonToJsonSchema(json)))

fun AutoJsonToJsonSchema(json: Jackson) = AutoJsonToJsonSchema(
    json,
    FieldRetrieval.compose(
        SimpleLookup(metadataRetrievalStrategy = JacksonFieldMetadataRetrievalStrategy),
        FieldRetrieval.compose(JacksonJsonPropertyAnnotated, JacksonJsonNamingAnnotated(json))
    )
)
The bit you need is to either: 1. replace JacksonFieldMetadataRetrievalStrategy in the above
2. use Jackson annoatations on the fields of your model
e
ah, okay.. I will play with it and see!
Thanks!
👍 1
I got confused with
renderer
and
OpenApi3Renderer
. Is there a way to use
OpenApi3
from jacksonExt.kt instead of a similar function from OpenApi3.kt?
d
just use this method and it will automatically pick it up
Copy code
OpenApi3(apiInfo, json, extensions, ApiRenderer.Auto(json, AutoJsonToJsonSchema(json)))
👍 1
152 Views