Jekyll2020-10-22T11:22:09+00:00https://ktor.kotlincn.net/feed.xmlKtorKtorOpenAPI generation in the Ktor plugin and website2018-08-07T00:00:00+00:002018-08-07T00:00:00+00:00https://ktor.kotlincn.net/blog/2018/08/openapi-gen<p>In this post I’m going to explain how the Ktor IntelliJ IDEA plugin generates code from Swagger/OpenAPI models
as 0.2.0 and why.</p>
<p>In case you didn’t know, Swagger/OpenAPI is a specification for defining APIs as Yaml or JSON model files.
And it has some tooling available: code generation from those models, simulators, and visualizers, etc.</p>
<p>But first, let’s take a look into how this all started:</p>
<h3 id="the-ktoriostart-project">The ktor.io/start project</h3>
<p>It was all started when <a href="https://hadihariri.com/" target="_blank">Hadi Hariri</a> suggested to creating a web application
where people could create Ktor projects directly in the browser and download a ready-to-use ZIP file.</p>
<p>This would help people get started with Ktor without having to manually setup the environment, since IntelliJ IDEA
is already able to open Gradle and Maven projects.</p>
<p>In any normal circumstances you would have created a full website, and rendered a ZIP file
directly from the backend, potentially using Ktor. But in fact, with Kotlin, there is a better way:
It just so happens that Kotlin supports generating JavaScript. It is called Kotlin/JS. And nowadays JavaScript
is so powerful that you can create a ZIP file in the frontend and download it to a local file without
requiring a backend at all.</p>
<p>I started this project in my own GitHub account, as a proposal. It lived some time in a custom URL
using the GitHub Pages.</p>
<p>After sometime it was promoted, moved to the ktorio organization, and now lives at <a href="https://ktor.io/start" target="_blank">https://ktor.io/start</a>.</p>
<h3 id="the-ktor-plugin-for-intellij-idea">The Ktor Plugin for IntelliJ IDEA</h3>
<p>In case you didn’t know, starting with Kotlin 1.2, there is experimental support for MPP (Multi Platform Projects)
in Kotlin. This means that you can create a project with several modules, some of them targeting JS, others the JVM,
and others a Common subset of Kotlin that can be referenced in JS and JVM projects. (And even if it isn’t relevant for this,
MPP also supports Kotlin/Native for targeting native executables without a VM).</p>
<p>This was a great opportunity to create a plugin for IntelliJ IDEA reusing the code for project generation and easily
maintaining both, the website and the plugin.</p>
<p>It worked like a charm, I was able to publish the IntelliJ IDEA plugin (that includes JVM code), and the ktor.io/start
(that includes Kotlin/JS code).</p>
<h3 id="in-the-meantime">In the meantime</h3>
<p>In the meantime, I was doing other things too that would later help to give form to the OpenAPI generator in the plugin:</p>
<h4 id="openapi-generation">OpenAPI generation</h4>
<p>I started <a href="https://github.com/swagger-api/swagger-codegen/pull/8092">far from ideal PR to the swagger-codegen project</a>.
It was far from ideal because I did it “fast” to get feedback and clean (squash, drop, adjust, etc.) the commits after the feedback.
It was like that also because the swagger-codegen project uses Java and Mustache for code generation.</p>
<p>I’m now used to using Kotlin, and Java requires a lot of ceremony for things that are super fast to code in Kotlin.
Also Mustache is not designed to handle indentations well and even when simple, it is a bit cryptic in a similar
fashion as clojure. I had to invest a lot of time, trying to get the right indentation. And being as Mustache is logicless
you end up having to put a lot of code on the Java-side for formatting and not just for the model part. So it was a bit awkward.</p>
<p>In the plugin code generation, I used a Kotlin DSL with an Indenter class, this allowed me to generate the code directly in
Kotlin without having to worry about indentation.</p>
<p>As an anecdote: just when I did the PR, the Swagger project had a major fork called OpenAPI with a lot of the main
developers from swagger moving there. The developer that did the initial Ktor generation suggested that I do the
PR there. But the problem was that the package names changed, and it would require a lot of extra effort from me, and maybe the generation was already not that good. So I deferred that effort until I had clear picture of how to generate the model properly.
I plan to make that PR someday, after we get some feedback from the plugin and the generated code stabilizes.</p>
<h4 id="ktor-springer-experiment">Ktor-Springer experiment</h4>
<p>I did an experiment with Ktor in my own user to define the routes in Ktor, instead with a DSL, with annotated methods,
similar to Spring. I created a repo here: <a href="https://github.com/soywiz/ktor-springer">https://github.com/soywiz/ktor-springer</a>.</p>
<p><a href="https://github.com/orangy">Ilya Ryzhenkov</a> suggested that I declare the routes as suspend methods, in an interface,
and then define the logic in a class implementing that interface. So we can reuse the interface to create a HTTP API
client calling those methods. That was a really interesting proposal, so I did it!</p>
<p>My main concern there was being able to check things from the request, or the headers without having to include
it as part of the method signature. But I had an idea: since all the API methods are <code class="language-plaintext highlighter-rouge">suspend</code>, we have access
to the <code class="language-plaintext highlighter-rouge">CoroutineContext</code>, and before calling the method, we can assign an object with the ApplicationCall to the
context, so that’s what I did. That only thing that is not that good is that right now Kotlin doesn’t support suspend properties.
So to access the call, instead of just typing <code class="language-plaintext highlighter-rouge">call</code>, you have to type <code class="language-plaintext highlighter-rouge">call()</code>. That’s a pretty minor issue though.</p>
<h3 id="importing-swaggeropenapi-in-the-generation-plugin">Importing Swagger/OpenAPI in the generation plugin</h3>
<p>A few weeks ago, I revisited the swagger-codegen idea. Since I was working on the plugin,
that already had a nice generic framework for code generation that worked in both JS and JVM.
Things linked together nicely, and I decided to just make the Swagger code generation a part of the plugin.</p>
<p>So I made a quick and dirty initial approach: parsed the JSON model files (YAML are left out at the moment for simplicity,
but you can convert YAML models to JSON easily with any tool that works for that), partially interpreted the most important parts
of the model, and rendered a simple set of route handlers for it.
It was incomplete, with no client generation, but the results were a good start for implementing a new API without
having to write it from scratch. I pushed it to the website since it doesn’t require plugin reviewing.</p>
<p>I had a week of holidays, and when returned back to work, decided to change the generated code to something else.</p>
<h3 id="server--client-code-generation">Server & Client code generation</h3>
<p>I wanted to generate both routes for Ktor, and a client using the asynchronous Ktor HttpClient. And I recalled the
ktor-springer experiment I did and wanted to try it out.</p>
<p>So what I did was to create all the models as data classes, and an interface with all the routes as suspend methods,
using all the information provided by the Swagger model: documentation for the route and fields, the path, and sources
for the parameters, etc.</p>
<p>Then I generated a class implementing those methods returning some stubs, and with dummy ifs throwing some potential
exceptions, so you can define the code. It also uses the JSON Schema Validation to validate parameters when available
as part of the model.</p>
<p>The good thing about this, is that by using the interface + a JVM Proxy and some tricks, I’m able to create a client
targeting a specific endpoint without any additional code. Which is pretty cool.</p>
<h3 id="ideas-for-the-future">Ideas for the future</h3>
<p>It would be nice if the client at least, works for MPP projects. But the JVM is the only target right now with full reflective capabilities.
With all the foundations set, we can change the generated code to something else, or for example to work
reflectionless by generating the code handling the routes, parsing the parameters, and calling the methods from the interface.
And the JSON parsing/generation can work using <a href="https://github.com/Kotlin/kotlinx.serialization">https://github.com/Kotlin/kotlinx.serialization</a>. So it should be viable.
As long as you keep your API as described in the model, the only code you have to change is the one for the server.</p>
<p>Another possible thing to do which would be nice is to generate a definition for <a href="https://ktor.io/quickstart/guides/api.html#first-request-intellij">the HTTP Client integrated with IntelliJ IDEA Ultimate</a>
to test the API directly in the IDE.</p>
<h3 id="a-quick-look-to-the-generated-code">A quick look to the generated code</h3>
<ul class="nav nav-tabs" id="myTab" role="tablist">
<li class="nav-item">
<a class="nav-link active" id="swagger-realworld-json-tab" data-toggle="tab" href="#swagger-realworld-json" role="tab" aria-controls="swagger-realworld-json" aria-selected="true"><code>swagger-realworld.json</code></a>
</li>
<li class="nav-item">
<a class="nav-link" id="application-kt-tab" data-toggle="tab" href="#application-kt" role="tab" aria-controls="application-kt" aria-selected="false"><code>application.kt</code></a>
</li>
<li class="nav-item">
<a class="nav-link" id="api-kt-tab" data-toggle="tab" href="#api-kt" role="tab" aria-controls="api-kt" aria-selected="false"><code>api.kt</code></a>
</li>
<li class="nav-item">
<a class="nav-link" id="backend-kt-tab" data-toggle="tab" href="#backend-kt" role="tab" aria-controls="backend-kt" aria-selected="false"><code>backend.kt</code></a>
</li>
<li class="nav-item">
<a class="nav-link" id="frontend-kt-tab" data-toggle="tab" href="#frontend-kt" role="tab" aria-controls="frontend-kt" aria-selected="false"><code>frontend.kt</code></a>
</li>
</ul>
<div class="tab-content" id="myTabContent">
<div class="tab-pane fade show active" id="swagger-realworld-json" role="tabpanel" aria-labelledby="swagger-realworld-json-tab">
<div class="code-snippet" data-src="/blog/samples/openapi/swagger.json" data-lang="json"></div>
</div>
<div class="tab-pane fade show" id="application-kt" role="tabpanel" aria-labelledby="application-kt-tab">
<div class="code-snippet" data-src="/blog/samples/openapi/application.kt" data-lang="kotlin"></div>
</div>
<div class="tab-pane fade show" id="api-kt" role="tabpanel" aria-labelledby="api-kt-tab">
<div class="code-snippet" data-src="/blog/samples/openapi/swagger-api.kt" data-lang="kotlin"></div>
</div>
<div class="tab-pane fade show" id="backend-kt" role="tabpanel" aria-labelledby="backend-kt-tab">
<div class="code-snippet" data-src="/blog/samples/openapi/swagger-backend.kt" data-lang="kotlin"></div>
</div>
<div class="tab-pane fade show" id="frontend-kt" role="tabpanel" aria-labelledby="frontend-kt-tab">
<div class="code-snippet" data-src="/blog/samples/openapi/swagger-frontend.kt" data-lang="kotlin"></div>
</div>
</div>
<p>This is the code the plugin is generating right now. It is not final, so any feedback is welcome!</p>In this post I’m going to explain how the Ktor IntelliJ IDEA plugin generates code from Swagger/OpenAPI models as 0.2.0 and why.Ktor IntelliJ IDEA 插件 0.2.02018-08-06T00:00:00+00:002018-08-06T00:00:00+00:00https://ktor.kotlincn.net/blog/2018/08/plugin-0.2.0<p>今天我们发布了 IntelliJ IDEA 插件的 0.2.0 版。</p>
<h3 id="swagger-20beta与-openapi-30alpha">Swagger 2.0(beta)与 OpenAPI 3.0(alpha)</h3>
<p>这个版本可以由 Swagger/OpenAPI 模型生成后端与前端代码。</p>
<ul>
<li>对 Swagger 2.0 JSON 模型的 Beta 支持。</li>
<li>对 OpenAPI 3.0.0 JSON 模型的 Alpha 支持(生成了一些代码但是缺少一些类以及其他内容)。</li>
<li>由模型生成文档化的接口。</li>
<li>生成实现了通过反射映射到指定路由的接口的类。</li>
<li>创建由 URL 端点与模型接口构造客户端的方法。</li>
<li>支持模型服务器端的 JSON 模式(JSON Schema)校验。</li>
</ul>
<p>可以试试<a href="https://github.com/ktorio/ktor-init-tools/blob/5f72587a95da0eabf4ce106c2ca31cffdc22a155/ktor-generator/jvm/testresources/swagger.json">现实世界的 swagger 模型</a>。</p>
<h3 id="修复">修复</h3>
<ul>
<li>修复了 Windows 上的代码生成问题</li>
<li>修复了网站中的 zip 文件夹权限。</li>
<li>修复了 maven 缺失属性。</li>
<li>现在生成空格而不是制表符</li>
</ul>
<h3 id="改进">改进</h3>
<ul>
<li>生成 <code class="language-plaintext highlighter-rouge">logback.xml</code> 文件。</li>
<li>其他模板。</li>
</ul>
<p>可以参阅<a href="/quickstart/quickstart/intellij-idea/plugin.html">更多关于该插件的信息</a>。</p>今天我们发布了 IntelliJ IDEA 插件的 0.2.0 版。Ktor 0.9.32018-06-26T00:00:00+00:002018-06-26T00:00:00+00:00https://ktor.kotlincn.net/blog/2018/06/0.9.3<p>Ktor 0.9.3 发布了!</p>
<p>Ktor 0.9.3 修复了一些 bug、升级了一些版本、提高了服务器的整体性能,
而且引入了一些新的 API 并更改了一些 API。</p>
<p>可以查看<a href="/quickstart/migration/0.9.3.html">从 0.9.2 迁移到 0.9.3</a>的文档。</p>Ktor 0.9.3 发布了!Ktor 0.9.22018-06-04T00:00:00+00:002018-06-04T00:00:00+00:00https://ktor.kotlincn.net/blog/2018/06/0.9.2<p>Ktor 0.9.2 发布了!</p>
<p>Ktor 0.9.2 修复了一些 bug、提高了服务器的整体性能、
开始部分支持 JVM 9,而且引入了一些新的 API 并更改了一些 API。</p>
<p>可以查看<a href="/quickstart/migration/0.9.2.html">从 0.9.1 迁移到 0.9.2</a>的文档。</p>Ktor 0.9.2 发布了!