How to expose swagger UI with http4k? - kotlin

I'm building a microservice with the http4k framework using their Contract APIs. I can easily expose the swagger API description JSON on eg. /swagger.json with
fun app(): HttpHandler = "/" bind contract {
renderer = OpenApi3(ApiInfo("GoOut Locations API", "1.0"), Jackson)
descriptionPath = "/swagger.json"
routes += ...
}
Is there an easy way to expose the swagger UI so that 1) I can specify the path it will be available on. (eg. /swagger-ui) 2) The UI will be preconfigured to fetch the description JSON from the descriptionPath specified above.
An ideal API would look something like
fun app(): HttpHandler = "/" bind contract {
renderer = OpenApi3(ApiInfo("GoOut Locations API", "1.0"), Jackson)
descriptionPath = "/swagger.json"
uiPath = "/swagger-ui"
routes += ...
}

After a bit of searching I achieved this with combination of Web Jars and http4k's static routing.
The potential viewer of the docs must simply visit /docs path where he gets redirected to /docs/index.html?url=<path to Api description> where
index.html is a static Swagger UI entrypoint served from a web jar.
url query param tells the swagger UI where to fetch the OpenApi description from.
From the DX perspective we have a simple http4k application:
// path the OpenApi description will be exposed on
private const val API_DESCRIPTION_PATH = "/swagger.json"
fun app(): HttpHandler {
val api = contract {
renderer = OpenApi3(ApiInfo("Your API summary", "1.0"), Jackson)
descriptionPath = API_DESCRIPTION_PATH
// the actual API routes
routes += ...
}
return routes(
// the docs routes are not considered part of the API so we define them outside of the contract
swaggerUi(API_DESCRIPTION_PATH),
api
)
}
The swaggerUi handler implementation follows
/**
* Exposes Swagger UI with /docs path as its entry point.
* #param descriptionPath absolute path to API description JSON. The UI will be configured to fetch it after load.
*/
fun swaggerUi(descriptionPath: String): RoutingHttpHandler = routes(
"docs" bind Method.GET to {
Response(Status.FOUND).header("Location", "/docs/index.html?url=$descriptionPath")
},
// For some reason the static handler does not work without "/" path prefix.
"/docs" bind static(Classpath("META-INF/resources/webjars/swagger-ui/3.25.2"))
)
We also have to include the swagger-ui webjar as our dependency. Here's a Gradle directive:
implementation 'org.webjars:swagger-ui:3.25.2'
See the webjars website for Maven (and more) directives.
Note that the swaggerUi handler assumes its bound to the / root path of the whole service. However, that can be easily fixed.

As of http4k 4.28.1.0, there is now a way to do this. See the following code taken from this documentation page:
package guide.howto.create_a_swagger_ui
import org.http4k.contract.contract
import org.http4k.contract.meta
import org.http4k.contract.openapi.ApiInfo
import org.http4k.contract.openapi.v3.OpenApi3
import org.http4k.contract.ui.swaggerUi
import org.http4k.core.Body
import org.http4k.core.ContentType
import org.http4k.core.Method.GET
import org.http4k.core.Request
import org.http4k.core.Response
import org.http4k.core.Status.Companion.OK
import org.http4k.core.Uri
import org.http4k.core.with
import org.http4k.lens.string
import org.http4k.routing.routes
import org.http4k.server.SunHttp
import org.http4k.server.asServer
fun main() {
val greetingLens = Body.string(ContentType.TEXT_PLAIN).toLens()
// Define a single http route for our contract
val helloHandler = "/v1/hello" meta {
operationId = "v1Hello"
summary = "Say Hello"
returning(OK, greetingLens to "Sample Greeting")
} bindContract GET to { _: Request ->
Response(OK).with(greetingLens of "HI!")
}
// Define a contract, and render an OpenApi 3 spec at "/spec"
val v1Api = contract {
routes += helloHandler
renderer = OpenApi3(
ApiInfo("Hello Server - Developer UI", "99.3.4")
)
descriptionPath = "spec"
}
// Build a Swagger UI based on the OpenApi spec defined at "/spec"
val ui = swaggerUi(
Uri.of("spec"),
title = "Hello Server",
displayOperationId = true
)
// Combine our api, spec, and ui; then start a server
// The Swagger UI is available on the root "/" path
routes(v1Api, ui)
.asServer(SunHttp(8080))
.start()
.block()
}

The solution using the webjar does not work anymore for SwaggerUI version >= 4.1.3 as the url parameter is ignored (see this issue / the release notes). The URL has to be either specified in the HTML or the url parameter needs to be enabled in the HTML.
So for now the solution seems to be to unpack the UI, update index.html, and serve directly rather through the webjar.

http4k doesn't ship with a version of the OpenApi UI. You can easily ship a version of the UI though by:
unpacking the OpenApi UI into the src/main/resources/public folder
Using a static routing block to server the resources. There is an example of that here: https://github.com/http4k/http4k-by-example/blob/22dcc9a83c497253c29830d5bc981afa5fbbe4ff/src/main/kotlin/verysecuresystems/SecuritySystem.kt#L61

Related

Ktor Resources type-safe routing plugin to generate URLs and paths

I have recently updated current Ktor version to 2.1.x and Locations plugin is now deprecated. I'm refactoring code to use Resources plugin for type-safe routing. The documentation is lacking how to generate paths from Resources objects. I'm trying to create a redirect address based on a Resource and achieve a functionality like in Locations plugin to build URLs
val path = application.locations.href(Listing(name = "movies", page = 10, count = 20))
Resource definition
#Serializable
#Resource("clients")
class Clients() {
#Serializable
#Resource("{clientID}")
class ID(val parent: Clients, val clientID: Int) {}
}
Route definition
fun Route.clientRouting(dao: ClientDao) {
route("") {
get<Clients> {
call.respondText("Hello Client!")
}
get<Clients.ID> { clientID ->
call.respondRedirect(...) //Redirect to Clients route
}
}
}
So... Is there a standard way of generating paths and URLs from Resources?
You can call the href method on an Application instance to get a URL for a specified resource:
get<Clients.ID> { clientID ->
call.respondRedirect(call.application.href(Clients()))
}

How to list configured routes in Ktor

I am setting up a project with multiple modules that contain different versions of api.
To verify correct route configuration I would like to print configured routes to application log like it is done in spring framework.
Is it possible and what should I use for that?
You can do it by recursively traversing routes starting from the root all the way down the tree and filtering in only ones with the HTTP method selector. This solution is described here.
fun Application.module() {
// routing configuration goes here
val root = feature(Routing)
val allRoutes = allRoutes(root)
val allRoutesWithMethod = allRoutes.filter { it.selector is HttpMethodRouteSelector }
allRoutesWithMethod.forEach {
println("route: $it")
}
}
fun allRoutes(root: Route): List<Route> {
return listOf(root) + root.children.flatMap { allRoutes(it) }
}
Please note that the code above must be placed after the routing configuration.

Get Swagger URL programmatically in ASP.NET Core

I'm using ASP.NET Core 5, and Swagger. I know how to use Swagger, and it works properly.
Swagger is served on foo:5001/swagger - but I need to determine that URL programmatically at runtime.
How can I do that?
I already tried:
Getting it by injecting IEnumerable<EndpointDataSource> into some helper/controller class, but that shows me all routes EXCEPT swagger's.
Getting it while setting up endpoint routing and inspecting IEndpointRouteBuilder, but once again it shows me all routes EXCEPT swagger's.
According to sources at https://github.com/domaindrivendev/Swashbuckle.AspNetCore/blob/master/src/Swashbuckle.AspNetCore.SwaggerUI/SwaggerUIMiddleware.cs you can use an instance of class SwaggerUIOptions:
Register instance in DI container:
var options = new SwaggerUIOptions
{
RoutePrefix = "swagger"
};
options.SwaggerEndpoint("/swagger/v1/swagger.json", "waiting_list v1");
services.AddSingleton(options);
Use configured instance:
app.UseSwaggerUI(app.ApplicationServices.GetRequiredService<SwaggerUIOptions>());
Inject instance to any controller/class:
public WeatherForecastController(ILogger<WeatherForecastController> logger, SwaggerUIOptions swaggerOptions)
{
}
Property RoutePrefix contains swagger prefix (without leading '/')
This idea works only if options object passed to UseSwaggerUI method (available since version 6.0.0). If UseSwaggerUI invoked using callback (like a UseSwaggerUI(a => { a.RoutePrefix = string.Empty; })) it won't work.

ASP.net Core web API Swagger UI version field - Is it possible to set this value in code?

I have a ASP.Net Core Web API with Swagger configured that shows the API End Points.Also API Versioning is enabled. However, the swagger UI is not populating the mandatory field Version when checking the End Point.See image below:
Is it possible to populate this field automatically by code given that the API Action already configures this value i.e. the MaptoApiVersion. In theory this field should be populated automatically??
[MapToApiVersion("2")]
[HttpGet("GetV2")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public async Task<IEnumerable<TodoDto>> GetV2()
{
var query = new AllTodosQuery(_context);
var todos = await query.ExecuteAsync();
return todos;
}
The issue is at least two-fold. First, the extensions to the API Explorer from API Versioning do provide the version parameter with a default value, but many Swagger/OpenAPI generators (such as Swashbuckle) still do not yet honor it. If you want to enable this behavior, you need a custom IOperationFilter which does something to the effect of:
var parameter = operation.Parameters.First(p => p.Name == "version");
var description = context.ApiDescription.ParameterDescriptions.First(p => p.Name == "version");
if (parameter.Schema.Default == null && description.DefaultValue != null)
{
parameter.Schema.Default = new OpenApiString(description.DefaultValue.ToString());
}
You can find a complete end-to-end example in the API Versioning repo in:
SwaggerDefaultValues.cs
Since you're versioning by URL segment, if you want that inlined into the route template without a corresponding parameter, you need only configure the API Explorer extensions to do so like this:
services.AddVersionedApiExplorer(options => options.SubstituteApiVersionInUrl = true);
This option only applies to the URL segment versioning method.
A complete end-to-end Swashbuckle example with API Versioning can be found inside Startup.cs inside the repo.

Swagger for Kotlin

Has anyone used a swagger tool with Kotlin?
In our organization, we have create most of our REST services using Java and SpringMVC (#RestController classes). We have used springfox to generate the Swagger API documentation. The swagger JSON representation is also used to automatically feed a searchable service catalog, so the swagger format for service metadata is important to us.
Some dev teams are now beginning to use Kotlin. We're looking for recommendations or comments related to using springfox or other swagger lib with Kotlin.
Here is sample spring boot app with swagger:
#RestController
class MyController {
#ApiOperation(value = "doc header...", notes = "detailed doc...")
#RequestMapping(value = "/double", method = arrayOf(RequestMethod.GET))
fun doubleValue(number: Int) = 2 * number
}
#Configuration
#EnableSwagger2
class SwaggerConfig {
#Bean
fun api(): Docket {
return Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any())
.build()
}
}
dependencies are
compile("io.springfox:springfox-swagger2:2.7.0")
compile("io.springfox:springfox-swagger-ui:2.7.0")
If you browse http://localhost:8080/swagger-ui.html it is all there...
I recently had a similar requirement. As a result I created a template project that integrates Kotlin, Webflux, and Swagger. It provides interactive API doc and automatic request validation.
See here -> https://github.com/cdimascio/kotlin-swagger-spring-functional-template
Validation is functional. It's used as such:
validate.request(req) {
// Do stuff e.g. return a list of names
ok().body(Mono.just(listOf("carmine", "alex", "eliana")))
}
with body
validate.request(req).withBody(User::class.java) { body ->
// Note that body is deserialized as User!
// Now you can do stuff.
// For example, lets echo the request as the response
ok().body(Mono.just(body))
}
It utilizes openapi 2 and 3 validation provided by atlassian.