papsign / ktor-openapi-generator Goto Github PK
View Code? Open in Web Editor NEWKtor OpenAPI/Swagger 3 Generator
License: Apache License 2.0
Ktor OpenAPI/Swagger 3 Generator
License: Apache License 2.0
For example, I have the following endpoint:
delete<ProductIdParam, SuccessResult>(
info(
summary = "Remove a product.",
description = "The product is removed only if a product with the same ID exists. Returns `${SuccessResult::class.simpleName}` saying whether the product has been removed."
),
example = SuccessResult.OK,
body = { param -> removeProduct(param.id, this::respond) }
)
I want to specify different examples, for example, with different status codes. So maybe example
should be renamed to examples
and receive an iterable. Possible usage:
delete<ProductIdParam, SuccessResult>(
info(
summary = "Remove a product.",
description = "The product is removed only if a product with the same ID exists. Returns `${SuccessResult::class.simpleName}` saying whether the product has been removed."
),
examples = listOf(SuccessResult.OK, SuccessResult.NOT_OK),
body = { param -> removeProduct(param.id, this::respond) }
)
The same for other methods (and parameters like exampleResponse
).
Hi! I've just copy-pasted the sample and gotten this:
The solution has been to update my build.gradle
specifying jvmTarget
explicitly: https://stackoverflow.com/a/50991772.
Actually, I've never had such an issue with other libs. Maybe there is a way to change the build script or something else somehow to ease the lib usage... Or maybe just giving a warning in README is enough ๐
Creating this because of #28
Currently there is no system to handle errors that occurred during runtime with this library. Instead i opted to use default values when parsing fails as it has best risk/effort ratio of the low effort options.
But now comes the time to implement it properly.
What it should do:
Hi!
In https://github.com/SerVB/e-shop/tree/938d357e4ff2b4e06a557102c65f73773b4d67c3, I have two similar tests (in server/src/test/kotlin/io/github/servb/eShop/product/route/singleOperation
dir):
When I run tests, I get the following:
For POST variant, both call.response.status()
and call.response.content
are null
. So the test fails.
Any ideas how can I test it?
We've recently started using this project, and we find it provides great value. One issue we're having, however, is code duplication (or alternatively the need for wrappers etc) in our "DTO" classes due to the fact that Ktor-OpenAPI-Generator seems to require HTTP response codes to be specified using the Response-annotation. Very often, the structures we want to return from GET, PUT and POST are the same, except for the http response code - a CRUD-api will want 201 for the POST, but 200 for the GET and PUT, for instance. The only way we've found around this is by having generic wrapper DTOs, but that means we can't have documentation that's specific to the endpoint.
It would be nice if the success status code for an endpoint could be passed as an argument to the post,get,put,etc functions - maybe as an argument to the info()-method?
This dosen't support routes moduling ?
Just a wild idea. How about allowing routing like get("path", ::someMethod)
and the parameters of that method are annotated the same way the fields of parameter classes are now annotated.
That way you don't have to declare a class for just one or two simple parameters and you get a very natural looking routing style.
Also this would be quite familiar to users of springfox.
I must be missing something completely. My usecase is a simple GET endpoint taking a list of Long ids and returning associated data.
I modelled the input parameter "ids" as
data class ProductImagesRequest(@QueryParam("multiple ids.") val ids:List<Long>)
When calling it with parameters like ?ids=1&ids=2&ids=3
I get this error:
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `java.util.ArrayList<java.lang.Long>` out of VALUE_STRING token
Why would a request parameter be deserialized from xml or json?
I love this library BTW, it's a very elegant solution!
Can I use different body types on success and on error? Because in Retrofit Im using different bodies with different types
Hi, I just saw this project and it looks very, very interesting. I am trying to figure out how this project differs to https://github.com/nielsfalk/ktor-swagger
Does this have any features the other one does not have, or is it simply different api, etc?
Hi! Is there an opportunity to change the "Models" section?
Currently, I have the following one (on this commit):
I'm not an experienced Swagger user but I think the issues are:
T
model should not be presented.minimum: null/maximum: null
for string
and boolean
seem redundant.minimum: -2147483648
for id: int32
isn't appropriate, I want it to start with 0
.kotlin.collections.List
is strange, it's a simple JSON array...io.github.servb.eShop.util.OptionalResult<io.github.servb.eShop.route.product.v1.ProductUsable>
has the data: T
field, but data: io.github.servb.eShop.route.product.v1.ProductUsable
is more logical here.I've briefly checked the annotations but haven't found suitable ones.
Sometimes when I run tests, I get the following exception for all tests:
java.lang.IndexOutOfBoundsException: Empty list doesn't contain element at index 0.
at kotlin.collections.EmptyList.get(Collections.kt:35)
at kotlin.collections.EmptyList.get(Collections.kt:23)
at com.papsign.ktor.openapigen.schema.builder.provider.DefaultMapSchemaProvider$Builder.build(DefaultMapSchemaProvider.kt:22)
at com.papsign.ktor.openapigen.schema.builder.provider.FinalSchemaBuilderProvider$Builder.build(FinalSchemaBuilderProvider.kt:70)
at com.papsign.ktor.openapigen.schema.builder.provider.DefaultObjectSchemaProvider$Builder$build$1.invoke(DefaultObjectSchemaProvider.kt:50)
at com.papsign.ktor.openapigen.schema.builder.provider.DefaultObjectSchemaProvider$Builder$build$1.invoke(DefaultObjectSchemaProvider.kt:30)
at com.papsign.ktor.openapigen.schema.builder.provider.DefaultObjectSchemaProvider$Builder.build(DefaultObjectSchemaProvider.kt:39)
at com.papsign.ktor.openapigen.schema.builder.provider.FinalSchemaBuilderProvider$Builder.build(FinalSchemaBuilderProvider.kt:70)
at com.papsign.ktor.openapigen.schema.builder.FinalSchemaBuilder$DefaultImpls.build$default(FinalSchemaBuilder.kt:8)
at com.papsign.ktor.openapigen.content.type.ktor.KtorContentProvider.getMediaType(KtorContentProvider.kt:63)
at com.papsign.ktor.openapigen.modules.handlers.ThrowOperationHandler.configure(ThrowOperationHandler.kt:27)
at com.papsign.ktor.openapigen.modules.handlers.RouteHandler.configure(RouteHandler.kt:32)
at io.github.servb.eShop.handler.product.v1.CreateProductKt.createProduct(createProduct.kt:169)
at io.github.servb.eShop.route.product.v1.AddProductV1RoutesKt.addProductV1Routes(addProductV1Routes.kt:7)
at io.github.servb.eShop.ApplicationKt$module$4.invoke(Application.kt:106)
at io.github.servb.eShop.ApplicationKt$module$4.invoke(Application.kt)
at com.papsign.ktor.openapigen.route.RouteConfigKt$apiRouting$1.invoke(RouteConfig.kt:17)
at com.papsign.ktor.openapigen.route.RouteConfigKt$apiRouting$1.invoke(RouteConfig.kt)
at io.ktor.routing.RoutingKt.routing(Routing.kt:120)
at com.papsign.ktor.openapigen.route.RouteConfigKt.apiRouting(RouteConfig.kt:13)
at io.github.servb.eShop.ApplicationKt.module(Application.kt:89)
at io.github.servb.eShop.product.InMemoryEShopProductKt.inMemoryEShopProduct(inMemoryEShopProduct.kt:6)
at io.github.servb.eShop.product.route.singleOperation.EShopProductCreateProductTest$1$1$1.invoke(EShopProductCreateProductTest.kt:20)
at io.github.servb.eShop.product.route.singleOperation.EShopProductCreateProductTest$1$1$1.invoke(EShopProductCreateProductTest.kt:18)
at io.ktor.server.testing.TestEngineKt$withTestApplication$1.invoke(TestEngine.kt:67)
at io.ktor.server.testing.TestEngineKt$withTestApplication$1.invoke(TestEngine.kt)
at io.ktor.server.testing.TestEngineKt.withApplication(TestEngine.kt:49)
at io.ktor.server.testing.TestEngineKt.withApplication$default(TestEngine.kt:43)
at io.ktor.server.testing.TestEngineKt.withTestApplication(TestEngine.kt:66)
at io.github.servb.eShop.util.KotlinFutureKt.withTestApplication(KotlinFuture.kt:13)
at io.github.servb.eShop.product.route.singleOperation.EShopProductCreateProductTest$1$1.invokeSuspend(EShopProductCreateProductTest.kt:20)
at io.github.servb.eShop.product.route.singleOperation.EShopProductCreateProductTest$1$1.invoke(EShopProductCreateProductTest.kt)
at io.kotest.core.spec.style.BehaviorSpecDsl$addGivenContext$1.invokeSuspend(behaviorSpecDsl.kt:25)
at io.kotest.core.spec.style.BehaviorSpecDsl$addGivenContext$1.invoke(behaviorSpecDsl.kt)
at io.kotest.core.runtime.ExecutionsKt$executeWithTimeout$2$1.invokeSuspend(executions.kt:16)
at io.kotest.core.runtime.ExecutionsKt$executeWithTimeout$2$1.invoke(executions.kt)
at io.kotest.core.AsserterKt.executeWithGlobalAssertSoftlyCheck(Asserter.kt:37)
at io.kotest.core.runtime.ExecutionsKt$executeWithTimeout$2.invokeSuspend(executions.kt:16)
at io.kotest.core.runtime.ExecutionsKt$executeWithTimeout$2.invoke(executions.kt)
at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturnIgnoreTimeout(Undispatched.kt:102)
at kotlinx.coroutines.TimeoutKt.setupTimeout(Timeout.kt:78)
at kotlinx.coroutines.TimeoutKt.withTimeout(Timeout.kt:31)
at io.kotest.core.runtime.ExecutionsKt.executeWithTimeout--MKxnPQ(executions.kt:13)
at io.kotest.core.runtime.TestExecutor$executeAndWait$2$1$3.invokeSuspend(TestExecutor.kt:181)
at io.kotest.core.runtime.TestExecutor$executeAndWait$2$1$3.invoke(TestExecutor.kt)
at io.kotest.core.runtime.ReplayKt.replay(replay.kt:19)
at io.kotest.core.runtime.TestExecutor$executeAndWait$2$1.invokeSuspend(TestExecutor.kt:176)
at io.kotest.core.runtime.TestExecutor$executeAndWait$2$1.invoke(TestExecutor.kt)
at io.kotest.core.runtime.ExecutorExecutionContext$executeWithTimeoutInterruption$$inlined$suspendCoroutine$lambda$2.invokeSuspend(ExecutorExecutionContext.kt:47)
(Coroutine boundary)
at io.kotest.core.runtime.TestExecutor$executeAndWait$2$1$3.invokeSuspend(TestExecutor.kt:181)
at io.kotest.core.runtime.ReplayKt.replay(replay.kt:19)
at io.kotest.core.runtime.TestExecutor$executeAndWait$2$1.invokeSuspend(TestExecutor.kt:180)
Caused by: java.lang.IndexOutOfBoundsException: Empty list doesn't contain element at index 0.
at kotlin.collections.EmptyList.get(Collections.kt:35)
at kotlin.collections.EmptyList.get(Collections.kt:23)
at com.papsign.ktor.openapigen.schema.builder.provider.DefaultMapSchemaProvider$Builder.build(DefaultMapSchemaProvider.kt:22)
at com.papsign.ktor.openapigen.schema.builder.provider.FinalSchemaBuilderProvider$Builder.build(FinalSchemaBuilderProvider.kt:70)
at com.papsign.ktor.openapigen.schema.builder.provider.DefaultObjectSchemaProvider$Builder$build$1.invoke(DefaultObjectSchemaProvider.kt:50)
at com.papsign.ktor.openapigen.schema.builder.provider.DefaultObjectSchemaProvider$Builder$build$1.invoke(DefaultObjectSchemaProvider.kt:30)
at com.papsign.ktor.openapigen.schema.builder.provider.DefaultObjectSchemaProvider$Builder.build(DefaultObjectSchemaProvider.kt:39)
at com.papsign.ktor.openapigen.schema.builder.provider.FinalSchemaBuilderProvider$Builder.build(FinalSchemaBuilderProvider.kt:70)
at com.papsign.ktor.openapigen.schema.builder.FinalSchemaBuilder$DefaultImpls.build$default(FinalSchemaBuilder.kt:8)
at com.papsign.ktor.openapigen.content.type.ktor.KtorContentProvider.getMediaType(KtorContentProvider.kt:63)
at com.papsign.ktor.openapigen.modules.handlers.ThrowOperationHandler.configure(ThrowOperationHandler.kt:27)
at com.papsign.ktor.openapigen.modules.handlers.RouteHandler.configure(RouteHandler.kt:32)
at io.github.servb.eShop.handler.product.v1.CreateProductKt.createProduct(createProduct.kt:169)
at io.github.servb.eShop.route.product.v1.AddProductV1RoutesKt.addProductV1Routes(addProductV1Routes.kt:7)
at io.github.servb.eShop.ApplicationKt$module$4.invoke(Application.kt:106)
at io.github.servb.eShop.ApplicationKt$module$4.invoke(Application.kt)
at com.papsign.ktor.openapigen.route.RouteConfigKt$apiRouting$1.invoke(RouteConfig.kt:17)
at com.papsign.ktor.openapigen.route.RouteConfigKt$apiRouting$1.invoke(RouteConfig.kt)
at io.ktor.routing.RoutingKt.routing(Routing.kt:120)
at com.papsign.ktor.openapigen.route.RouteConfigKt.apiRouting(RouteConfig.kt:13)
at io.github.servb.eShop.ApplicationKt.module(Application.kt:89)
at io.github.servb.eShop.product.InMemoryEShopProductKt.inMemoryEShopProduct(inMemoryEShopProduct.kt:6)
at io.github.servb.eShop.product.route.singleOperation.EShopProductCreateProductTest$1$1$1.invoke(EShopProductCreateProductTest.kt:20)
at io.github.servb.eShop.product.route.singleOperation.EShopProductCreateProductTest$1$1$1.invoke(EShopProductCreateProductTest.kt:18)
at io.ktor.server.testing.TestEngineKt$withTestApplication$1.invoke(TestEngine.kt:67)
at io.ktor.server.testing.TestEngineKt$withTestApplication$1.invoke(TestEngine.kt)
at io.ktor.server.testing.TestEngineKt.withApplication(TestEngine.kt:49)
at io.ktor.server.testing.TestEngineKt.withApplication$default(TestEngine.kt:43)
at io.ktor.server.testing.TestEngineKt.withTestApplication(TestEngine.kt:66)
at io.github.servb.eShop.util.KotlinFutureKt.withTestApplication(KotlinFuture.kt:13)
at io.github.servb.eShop.product.route.singleOperation.EShopProductCreateProductTest$1$1.invokeSuspend(EShopProductCreateProductTest.kt:20)
at io.github.servb.eShop.product.route.singleOperation.EShopProductCreateProductTest$1$1.invoke(EShopProductCreateProductTest.kt)
at io.kotest.core.spec.style.BehaviorSpecDsl$addGivenContext$1.invokeSuspend(behaviorSpecDsl.kt:25)
at io.kotest.core.spec.style.BehaviorSpecDsl$addGivenContext$1.invoke(behaviorSpecDsl.kt)
at io.kotest.core.runtime.ExecutionsKt$executeWithTimeout$2$1.invokeSuspend(executions.kt:16)
at io.kotest.core.runtime.ExecutionsKt$executeWithTimeout$2$1.invoke(executions.kt)
at io.kotest.core.AsserterKt.executeWithGlobalAssertSoftlyCheck(Asserter.kt:37)
at io.kotest.core.runtime.ExecutionsKt$executeWithTimeout$2.invokeSuspend(executions.kt:16)
at io.kotest.core.runtime.ExecutionsKt$executeWithTimeout$2.invoke(executions.kt)
at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturnIgnoreTimeout(Undispatched.kt:102)
at kotlinx.coroutines.TimeoutKt.setupTimeout(Timeout.kt:78)
at kotlinx.coroutines.TimeoutKt.withTimeout(Timeout.kt:31)
at io.kotest.core.runtime.ExecutionsKt.executeWithTimeout--MKxnPQ(executions.kt:13)
at io.kotest.core.runtime.TestExecutor$executeAndWait$2$1$3.invokeSuspend(TestExecutor.kt:181)
at io.kotest.core.runtime.TestExecutor$executeAndWait$2$1$3.invoke(TestExecutor.kt)
at io.kotest.core.runtime.ReplayKt.replay(replay.kt:19)
at io.kotest.core.runtime.TestExecutor$executeAndWait$2$1.invokeSuspend(TestExecutor.kt:176)
at io.kotest.core.runtime.TestExecutor$executeAndWait$2$1.invoke(TestExecutor.kt)
at io.kotest.core.runtime.ExecutorExecutionContext$executeWithTimeoutInterruption$$inlined$suspendCoroutine$lambda$2.invokeSuspend(ExecutorExecutionContext.kt:47)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:56)
at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:272)
at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:79)
at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:54)
at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source)
at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$default(Builders.kt:36)
at kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown Source)
at io.kotest.core.runtime.ExecutorExecutionContext.executeWithTimeoutInterruption-D5N0EJY(ExecutorExecutionContext.kt:46)
at io.kotest.core.runtime.TestExecutor$executeAndWait$2.invokeSuspend(TestExecutor.kt:168)
at io.kotest.core.runtime.TestExecutor$executeAndWait$2.invoke(TestExecutor.kt)
at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:91)
at kotlinx.coroutines.CoroutineScopeKt.coroutineScope(CoroutineScope.kt:177)
at io.kotest.core.runtime.TestExecutor.executeAndWait-xkB6VbI(TestExecutor.kt:165)
at io.kotest.core.runtime.TestExecutor.invokeTestCase(TestExecutor.kt:149)
at io.kotest.core.runtime.TestExecutor.executeActiveTest(TestExecutor.kt:116)
at io.kotest.core.runtime.TestExecutor$intercept$2.invokeSuspend(TestExecutor.kt:73)
at io.kotest.core.runtime.TestExecutor$intercept$2.invoke(TestExecutor.kt)
at io.kotest.core.runtime.TestExecutor.executeIfActive(TestExecutor.kt:85)
at io.kotest.core.runtime.TestExecutor.intercept(TestExecutor.kt:73)
at io.kotest.core.runtime.TestExecutor.execute(TestExecutor.kt:54)
at io.kotest.core.engine.SingleInstanceSpecRunner.runTest(SingleInstanceSpecRunner.kt:63)
at io.kotest.core.engine.SingleInstanceSpecRunner$execute$2.invokeSuspend(SingleInstanceSpecRunner.kt:74)
at io.kotest.core.engine.SingleInstanceSpecRunner$execute$2.invoke(SingleInstanceSpecRunner.kt)
at io.kotest.core.engine.SingleInstanceSpecRunner$execute$3.invokeSuspend(SingleInstanceSpecRunner.kt:80)
at io.kotest.core.engine.SingleInstanceSpecRunner$execute$3.invoke(SingleInstanceSpecRunner.kt)
at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:91)
at kotlinx.coroutines.CoroutineScopeKt.coroutineScope(CoroutineScope.kt:177)
at io.kotest.core.engine.SingleInstanceSpecRunner.execute(SingleInstanceSpecRunner.kt:78)
at io.kotest.core.engine.SpecExecutor$runTests$run$1.invokeSuspend(SpecExecutor.kt:105)
at io.kotest.core.engine.SpecExecutor$runTests$run$1.invoke(SpecExecutor.kt)
at io.kotest.core.engine.SpecExecutor.interceptSpec(SpecExecutor.kt:117)
at io.kotest.core.engine.SpecExecutor.runTests(SpecExecutor.kt:108)
at io.kotest.core.engine.SpecExecutor.execute(SpecExecutor.kt:36)
at io.kotest.core.engine.KotestEngine$submitBatch$$inlined$forEach$lambda$1$1.invokeSuspend(KotestEngine.kt:78)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:56)
at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:272)
at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:79)
at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:54)
at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source)
at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$default(Builders.kt:36)
at kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown Source)
at io.kotest.core.engine.KotestEngine$submitBatch$$inlined$forEach$lambda$1.run(KotestEngine.kt:77)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at java.base/java.lang.Thread.run(Thread.java:834)
Could you investigate?
In the models section, there is a field called "example". Currently it is equal to null
for all fields in all classes:
There should be a way to specify this field. Also, I think if the field is not specified, it shouldn't be presented in the models section.
This is an issue extracted from #12.
I am making DSL for your framework and when I'm trying to make wrapper function the method and getting an internal JVM exception cause of invalid bytecode generation
I haven't found the real solution, but in my case changing 'crossinline' modifier to 'noinline' helped, can you ask, why are you using the first one ?
or may be even can you replace crossinline into noinine ?
Another thing I just ran into and I am not sure on how to specify, what if my endpoint has different types it can return and multiple response codes?
Top be precise for that endpoint we return data class ErrorResponse(val message: String)
in cases where we return a 400, 404 and 500 status but not sure how I can define that.
Further when we may return a 201 with en empty response. How can this all be defined? It is not quite clear to me from the existing examples.
Sorry for opening one issue after another and thanks for being so quick to respond, but here is one more. As I am not always a fan of using git submodules I thought I would give jitpack a try since that has worked for me quite well so far, but for some reason the build for this project is broken, see: https://jitpack.io/#papsign/Ktor-OpenAPI-Generator/c2b7932414 and https://jitpack.io/com/github/papsign/Ktor-OpenAPI-Generator/c2b7932414/build.log
Not sure if it would be a lot of effort to get this building on jitpack but it could be nice to give people an alternative to using git submodules.
I really like this project, however, not only is documentation lacking, but the examples seem broken/half-baked (eg TestServer.kt
's APIPrincipal
) and some usages are far from understandable.
For example, I just (literally) got a headache trying to understand how the StatusPages
syntax works.
Let's give the original usage a look:
exception<Throwable> { cause -> // easy, when Throwable is caught, InternalServerError is returned
call.respond(HttpStatusCode.InternalServerError)
}
And the new usage:
exception<ProperException, Error>(HttpStatusCode.BadRequest) {
// ^ when ProperException is thrown (what's a ProperException???), and... Error??? (parameter "B"... very useful..) then we have hard-coded status code?
it.printStackTrace()
Error(it.id, it.localizedMessage) // and for some reason, return an Error
}
Similarly, the "basic" example in the readme also falls short in explaining anything and is hard to grasp:
//bare minimum, just like Ktor but strongly typed
get<StringParam, StringResponse> { params -> // ok... get what exactly? where are we?
respond(StringResponse(params.a))
}
route("inine").get<StringParam, StringResponse>( // what's "inline"??
info("String Param Endpoint", "This is a String Param Endpoint"), // A Route module that describes an endpoint, it is optional
example = StringResponse("Hi")
) { params ->
respond(StringResponse(params.a)) // what's params and why do we use params.a???
}
If writing proper examples and tests is too much effort, at least using better named symbols helps.
(to put some context, I spent half a day trying to convert two of my existing endpoints and spent most of the time digging in examples and internal source code trying to figure things out)
is it possible to have an endpoint with multiple query params?
for example, the following:
@Request("Query Params")
data class QueryParams(@QueryParam("a") val a: String?, @QueryParam("b") val b: String?)
apiRouting {
route("/data/get") {
get<QueryParams, List<Data>>(
info("Query Params Endpoint", "This is a Query Params Endpoint"),
example = mutableListOf(Data("SomeData"))
) { params ->
respond( Data("SomeData") )
}
}
}
results in:
Exception in thread "main" java.lang.IllegalStateException: Only data classes are currently supported for Parameter objects
at com.papsign.ktor.openapigen.parameters.parsers.converters.object.ObjectConverter.<init>(ObjectConverter.kt:25)
at com.papsign.ktor.openapigen.parameters.parsers.converters.object.ObjectConverter$Companion.create(ObjectConverter.kt:44)
at com.papsign.ktor.openapigen.parameters.parsers.converters.object.ObjectConverter$Companion.create(ObjectConverter.kt:37)
at com.papsign.ktor.openapigen.parameters.parsers.converters.ConverterSelectorFactory.buildConverter(ConverterSelectorFactory.kt:7)
at com.papsign.ktor.openapigen.parameters.parsers.builders.query.form.ConverterFormBuilder$Selector.canHandle(ConverterFormBuilder.kt:20)
at com.papsign.ktor.openapigen.parameters.parsers.builders.BuilderSelectorFactory.buildBuilder(BuilderSelectorFactory.kt:9)
at com.papsign.ktor.openapigen.parameters.parsers.builders.BuilderFactory$DefaultImpls.buildBuilderForced(BuilderFactory.kt:8)
at com.papsign.ktor.openapigen.parameters.parsers.builders.BuilderSelectorFactory.buildBuilderForced(BuilderSelectorFactory.kt:6)
Also, could you point me in the right direction of how to do this if it is possible?
I would like to generate client code from the generated swagger doc and use the first Tag and the operationId for naming the methods. I have found in the tests how to add tags, but I can't find anywhere how to add operationId.
I am using @path like
@Path("/{deviceId}")
data class MyParams(
@PathParam("Device ID") val deviceId: String
)
When i use it with route, proper openapi definitions are created but ktor route is not working. I listed routes and checked... It doesn't show as parameterized route and also none of post method works...
route("images") {
post<MyParams, PResponse, PRequest>(
info("Add to server", "Add link")
) { pr, req ->
respond(pr(true))
}
route listing on ktor show
19-04-2020 17:47:04.633 [main] INFO Application - route: /(method:GET)
19-04-2020 17:47:04.633 [main] INFO Application - route: /(authenticate "default")/users/agent/(method:GET)
19-04-2020 17:47:04.633 [main] INFO Application - route: /(authenticate "default")/images/(method:POST)
Sending port request to images/1 or images/ routes produces error 404
19-04-2020 18:00:01.192 [nioEventLoopGroup-4-3] INFO Application - Unhandled: POST - /images/
19-04-2020 18:00:32.333 [nioEventLoopGroup-4-3] INFO Application - Unhandled: POST - /images/1
Why route is shown unhandled and it is generated as above without parameters
Putting this in a separate issue from #12
@Min
and @Max
annotations are missing.
So, hopefully this will be the last one. In our application we have
install(Authentication) {
jwt(configure = authenticationProvider)
}
and I would like for routes within the apiRouting
block to be able to make use of that authentication provider. For normal kotlin routes I can just use the authenticate
block but that does not seem to exist in apiRouting
. Is this use-case supported or not?
A system needs to be created that allows to provide one or more examples with their metadata without adding bulk to the minimal configuration.
If I have a response class with a List<SomeEnum>
field, the generated entity looks like:
TheEntity:
type: object
properties:
test:
type: array
items:
$ref: "#/components/schemas/TheEnum"
TheEnum:
enum: ["A","B","C"]
nullable: false
type: string
That looks pretty good, but if instead I had Set<SomeEnum>
, I get:
TheEntity:
type: object
properties:
test:
$ref: "#/components/schemas/TheEnum"
Set_TheEnum_:
type: object
properties:
size:
type: integer
format: int32
nullable: false
minimum: -2147483648
maximum: 2147483647
required: ["size"]
nullable: false
I think the expected schema should have looked like:
TheEntity:
type: object
properties:
test:
type: array
items:
$ref: "#/components/schemas/TheEnum"
uniqueItems: true # <----- very important for sets
TheEnum:
enum: ["A","B","C"]
nullable: false
type: string
In the source code, I saw a few places with clazz.isSubclassOf(List::class)
, not sure but I'm thinking the fix involves changing those places.
Hey, thanks for this project, so far it looks great but I am running into some limitations with sealed classes. I have a model as follows:
data class Tablet(
val id: String,
val something: Something,
val apps: Map<String, App>
)
where-as app is a sealed class and while jackson 2.10.1 recognises that and converts incoming requests perfectly fine in the generated json schema App
is only displayed as an empty object. Are sealed classes meant to be supported? As far as I can tell that would probably map to a oneof, right?
Edit: For reference, here is btw the PR that added auto-discovery of sealed classes: FasterXML/jackson-module-kotlin#240
I want respond with byte array to user can read that as file
How can I mark file body and formData params ?
I don't find any Int
constants, so currently I suppose hardcoding like 404
is required.
And if somebody reworks Response
annotation so it accepts HttpStatusCode
instead of Int
, it will be a duplicated description (Response.description
and Response.statusCode.description
). There is another solution I see: leave only a single parameter in the Response
of HttpStatusCode
type but it's not pretty because of the class name. Actually, I've planed to open a PR with the first solution but now I don't know what's more elegant in that situation.
It's a related to #24 because it's about description too.
Hi this is nice, I am new to ktor.
would you please add how to use this in ktor project ?
like what to include in gradle.build file etc. step by step ?
Hi,
This library looks great!
I am having problems with getting things working in my Ktor application which uses kotlinx.serialization for API serialization/deserialization. I can't find if this library should work (or is tested) with kotlinx.serialization? And if so if there are any specific settings that need to be set?
When I do http://localhost:8080/my/dassda , I want to see a bad request, but I get I received 0
. Revision I use is be025c9. Is there a way to handle bad params?
@Path("{iii}")
data class MyParam(@PathParam("I'm int") val iii: Int)
route("my") {
throws(
status = HttpStatusCode.BadRequest,
example = "bad request",
exClass = Throwable::class
) {
get<MyParam, String> { param ->
respond("I received ${param.iii}")
}
}
}
Continuing #17.
I've just tried it (using 0d4f589 revision) and I've come up with the following:
data class SuccessResult(val ok: Boolean) {
companion object {
val NOT_OK = SuccessResult(ok = false)
val OK = SuccessResult(ok = true)
}
}
data class ProductUsable(val name: String, val id: Int, val type: Int)
fun NormalOpenAPIRoute.addRoutes() {
route("product") {
post<Unit, SuccessResult, ProductUsable>(
info(
summary = "Create a product.",
description = "The product is created only if a product with the same ID does not exist. Returns `${SuccessResult::class.simpleName}` saying whether the product has been created."
),
exampleResponse = SuccessResult.OK,
exampleRequest = exampleProductUsable,
body = { _, body -> createProduct(body, this::respond) }
)
// ...
}
}
data class SuccessResult(val ok: Boolean) {
companion object {
val NOT_OK = SuccessResult(ok = false)
val OK = SuccessResult(ok = true)
}
}
data class ProductUsable(val name: String, val id: Int, val type: Int)
private fun <T : OpenAPIRoute<T>> T.throwsSuccessResultNotOk(fn: T.() -> Unit) {
throws(
APIException.apiException(
status = HttpStatusCode.BadRequest,
example = SuccessResult.NOT_OK,
gen = { _: Throwable -> SuccessResult.NOT_OK }
),
fn = fn
)
}
fun NormalOpenAPIRoute.addRoutes() {
route("product") {
throwsSuccessResultNotOk {
post<Unit, SuccessResult, ProductUsable>(
info(
summary = "Create a product.",
description = "The product is created only if a product with the same ID does not exist. Returns `${SuccessResult::class.simpleName}` saying whether the product has been created."
),
exampleResponse = SuccessResult.OK,
exampleRequest = exampleProductUsable,
body = { _, body -> createProduct(body, this::respond) }
)
// ...
}
}
}
The usability problems I see:
NOT_OK
(which is false
):When the request is missing some primitive fields, zeros are inserted. Here are some examples:
ProductUsable(name = "a", id = 5, type = 42)
SuccessResult.OK
ProductUsable(name = "a", type = 42)
SuccessResult.NOT_OK
(because id
is missing)SuccessResult.OK
(transformed as ProductUsable(name = "a", id = 0, type = 42)
)ProductUsable(id = 5, type = 42)
SuccessResult.NOT_OK
"Gen" function looks duplicating logic of the "example".
I don't understand which exception I should specify in "gen" function.
Finally, I propose a bit different design. It's inspired by exception
block and I think it solves the 3 and the 4:
// inspired by:
//exception<JsonMappingException, Error>(HttpStatusCode.BadRequest) {
// it.printStackTrace()
// Error("mapping.json", it.localizedMessage)
//}
fun NormalOpenAPIRoute.addRoutes() {
route("product") {
catches<JsonMappingException, SuccessResult>( // means that when JsonMappingException happens (can't generate the request body object), "generator" is called
status = HttpStatusCode.BadRequest,
example = SuccessResult.NOT_OK,
generator = null // means that the example is returned
) {
post<Unit, SuccessResult, ProductUsable>(
info(
summary = "Create a product.",
description = "The product is created only if a product with the same ID does not exist. Returns `${SuccessResult::class.simpleName}` saying whether the product has been created."
),
exampleResponse = SuccessResult.OK,
exampleRequest = exampleProductUsable,
body = { _, body -> createProduct(body, this::respond) }
)
// ...
}
}
}
I would love to be able to annotate model properties with descriptions, like we can do with @HeaderParam
, @PathParam
, and @QueryParam
, and it doesn't look like that is currently possible.
I'd imagine something like the new @OpenAPIName
would work well.
It seems that the info in #8 (comment) is irrelevant: classes and functions are missing...
I want to make my own OAuth service to be used by the main service to verify requests.
I've created custom validators with the 0.1-beta.2 version and I wanted to update to the latest version (0.2-beta.5). The validation system changed a lot and I can't figure it out how to write a custom validator. I've only found example on custom validators for the old version.
Not issue, only info
kotlin packages for example.in domain with name like
package `in`.example
will need schemaNamer
schemaNamer = { kType ->
// Added ` in regex
val regex = Regex("[`A-Za-z0-9_.]+")
kType.toString().replace(regex) { it.value.split(".").last() }.replace(Regex(">|<|, "), "_")
}
In the models section, there are redundant fields:
Current workaround is activating mapper.setSerializationInclusion(Include.NON_NULL)
for Jackson.
However, it changes the way the server answers, so it's not a solution in some cases.
I think the solution can be just to omit generating minimum
and maximum
for string
and boolean
in the models section.
This is an issue extracted from #12.
I wanted to create a PR that would allow for not using Unit
when there are no route params (should be ready soon). However, when I was writing some unit test cases for the new code, I ran into a weird issue with the post route not being handled when the body was specified. What makes this weird is that running the same code works fine when standing up the server and sending real post requests.
This commit has a minimal server that shows the post working (ktor only, post route with Any
for body, post route with actually specifying body type) and a unit test cases that should represent those same three cases, but testPost_bodyType_expectedBodyAndResponse
fails.
Can you take a look? It is possible that I am missing something. I don't know enough about the ktor
internals to track down where the request is being rejected. And it is odd that only the test application fails.
Hi! I don't understand how to respond with a custom status code, for example, 404, depending on the situation.
I try the following: I declare an interface and set it as a response type. Then I create some children of the interface with specified status codes. Example:
interface IResponse {
val myField: Boolean
}
@Response(statusCode = 200)
object MyGoodResponse : IResponse {
override val myField = true
}
@Response(statusCode = 404)
object MyBadResponse : IResponse {
override val myField = false
}
// ...
route("a") {
get<Unit, IResponse> {
if (System.currentTimeMillis() % 2 == 0L) {
respond(MyGoodResponse)
} else {
respond(MyBadResponse)
}
}
}
I want to see 404, but it's 200:
Is it supported now?
If you want to take a look a real-world example, please fresh start this revision of my project and call GET http://localhost:8080/v1/product/0, the status should be 404 but it returns OK and this test fails: https://github.com/SerVB/e-shop/blob/37958c8bb4fee8a42dad229c578f79867c1b5d74/server/src/test/kotlin/io/github/servb/eShop/product/route/singleOperation/EShopProductReturnProductTest.kt .
It seems that header parameters can be supported: https://rajeevdotnet.blogspot.com/2018/03/swagger-ui-how-to-add-custom-header.html.
inline fun NormalOpenAPIRoute.tag(name: String = "", description: String = "", crossinline fn: NormalOpenAPIRoute.() -> Unit) { tag(object : APITag { override val description = description override val name = name }, fn) }
Sorry now I have a little bit time to create PM. You can add this code to create tags with name and description instead of implementing interface (or write if this functionality already exists)
I am trying to do custom token based authentication on whole route but using ktor authentication is not working. How to add authentication method for openapi?
fun Routing.userRoute() {
apiRouting {
authenticate {
route("users", Tags.USER) {
route("agent").get<Unit, UserAgentResponse>(
info("Get Agent", "Get agent used to get images from server"),
example = UserAgentResponse("qwerty")
) {
respond(UserAgentResponse(Helper.userAgent))
}
}
}
}
}
I am trying to create route to receive upload file to server but not able to find a way to implement it. I have seen multipart content provider in code but not sure how to use it.
How to write post data server code for multipart something like this:
https://ktor.io/servers/uploads.html
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.