B-Teck!

お仕事からゲームまで幅広く

JSONからJSON Schemaを生成してYAMLに変換する

YAMLで書いてるSwaggerの定義に実装済みのAPIを記述したいが、
レスポンスの定義を手で書くのはしんどい…みたいなときに、
実際に返却しているJSONから生成できれば便利では!?と思ってやってみた。

TL;DR

  • QuicktypeでJSONからJSON Schemaを生成
  • json2yamlでJSON Schemaをyamlに変換
  • Swaggerで読み取れて便利

手順

不要そうなところは各自適宜飛ばしてください。

Quicktypeのインストール

# quicktypeインストールのための手順
# Homebrewインストール
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
# node.js(npm)インストール
brew install nodebrew
echo 'export PATH=$HOME/.nodebrew/current/bin:$PATH' >> ~/.bash_profile
nodebrew install-binary latest # 必要に応じて変えてください
# quicktypeインストール
npm install -g quicktype

json2yamlのインストール

# json2yamlインストールのための手順
# MacはデフォルトでPython2.7がインストールされているのでそのまま使う
# (2019/04/24時点)
# pipインストール
curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
python get-pip.py --user
rm get-pip.py 
# パスを通す
export PATH="$HOME/Library/Python/2.7/bin:$PATH"
echo 'export PATH="$HOME/Library/Python/2.7/bin:$PATH"' >> ~/.bash_profile

# json2yamlインストール
pip install json2yaml --user

実行例

あくまで食わせたJSONの定義のみを吐き出すので、実際のrequiredと正しいかとか、
細かいオブジェクトが網羅できているかはチェックが必要なので注意。

# homeにおいたresponse.jsonをschema.yamlに変換して吐き出す
quicktype response.json --lang schema | json2yaml > schema.yaml

生成元のjson

{
  "foo": [ 1, null ],
  "baz": {
    "foo": [ true, "bar" ],
    "baz": "qux"
  }
}

yaml変換後のSchema

$schema: http://json-schema.org/draft-06/schema#
$ref: '#/definitions/Test'
definitions:
  Test:
    type: object
    additionalProperties: false
    properties:
      foo:
        type: array
        items:
          anyOf:
          - type: integer
          - type: 'null'
      baz:
        $ref: '#/definitions/Baz'
    required:
    - baz
    - foo
    title: Test
  Baz:
    type: object
    additionalProperties: false
    properties:
      foo:
        type: array
        items:
          $ref: '#/definitions/Foo'
      baz:
        type: string
    required:
    - baz
    - foo
    title: Baz
  Foo:
    anyOf:
    - type: boolean
    - type: string
    title: Foo

平成最後の記事更新でした。
令和でまたお会いしましょう。

【Kotlin】JerseyをKotlinで動かす その2 パラメータの取扱編

前回の記事 beatdjam.hatenablog.com

前回はJerseyの環境づくりと起動方法、Kotlinへの変換について書きました。
今回は各種リクエストパラメータをどうやって取り扱うかを説明します。

準備

今回の記事の内容を扱うにあたり、新しいResouceクラスを作成しましょう。
下記の形でParametersResource.ktを作成します。
このクラスには @Path("parameters") アノテーションがついているので、
http://localhost:8080/myapp/parameters に対応するクラスとなります。
クラスに@Pathがついている状態でメソッドにも@Pathがついているとき、
クラスのpath/メソッドのpathに対応します。

package com.example

import com.example.form.BeanParamSample
import java.nio.charset.Charset
import javax.validation.Valid
import javax.ws.rs.*
import javax.ws.rs.core.MediaType

/**
 * Root resource (exposed at "myresource" path)
 */
@Path("parameters")
class ParametersResource {

}

HTTPリクエストのクエリ

HTTPリクエストのクエリに含まれている値を利用するには、@QueryParam を使います。
例)
@QueryParam("parameter") parameter: String?

http://localhost:8080/myapp/parameters/queryparam?parameter=hoge

    @GET
    @Path("/queryparam")
    @Produces(MediaType.TEXT_PLAIN)
    fun queryParam(@QueryParam("parameter") parameter: String?): String {
        return if (parameter.isNullOrEmpty()) {
            "Parameter is Empty."
        } else parameter
    }

クエリをdata classに対応させる

前述したQueryParamをdata classのフィールドに定義します。
これを、対応させたいResourceクラスで@BeanParam アノテーションをつけて記述すると、
リクエスト時に自動でdata classに格納してくれるようになります。
後述する @PathParam@FormParamでも利用できるので覚えておきましょう。

また、JerseyではJavaEEのBean Validationという仕組みが利用できます。
@field:NotNull@field:NotEmpty などのバリデーション用のアノテーションを設定すると、
正しくないリクエストに対して、自動で400 Bad Requestを返却してくれるものです。

data class BeanParamSample (
        @QueryParam("parameter1")
        val parameter1: String?,

        @QueryParam("parameter2")
        @field:NotNull
        val parameter2: String?,

        @QueryParam("parameter3")
        @field:NotEmpty
        val parameter3: String?
)

 

@GET
@Path("/beanparam")
@Produces(MediaType.TEXT_PLAIN)
fun beanparam(@BeanParam @Valid parameter: BeanParamSample): String {
    return buildString {
        appendln(
                if (parameter.parameter1.isNullOrEmpty()) {
                    "Parameter is Empty."
                } else parameter.parameter1
        )
        appendln(parameter.parameter2)
        appendln(parameter.parameter3)
    }
}

Pathに含まれる値

HTTPリクエストのパスに含まれる値を利用するためには @PathParam アノテーションを利用します。
また、@Path の指定時にパラメータとしたい箇所を{}でくくる必要があります。
例)
@Path("/pathparam/{parameter1}
@PathParam("parameter") parameter: String?

http://localhost:8080/myapp/parameters/pathparam/hogehoge

@GET
@Path("/pathparam/{parameter}")
@Produces(MediaType.TEXT_PLAIN)
fun pathParam(@PathParam("parameter") parameter: String?): String {
    return if (parameter.isNullOrEmpty()) {
        "Parameter is Empty."
    } else parameter
}

リクエストパスからパターンに対応した値を取り出す

パターンに対応する文字列から複数を取り出すような事もできます。
{parameter1}.{parameter2} のようなパターンを設定すると、
0000.1234 のようなパスの00001234 をそれぞれ取得できます。
例)
@Path("/pathparam/{parameter1}.{parameter2}")
@PathParam("parameter1") parameter1: String?
@PathParam("parameter2") parameter2: String?

http://localhost:8080/myapp/parameters/pathparam/1234.5678

@GET
@Path("/pathparam/{parameter1}.{parameter2}")
@Produces(MediaType.TEXT_PLAIN)
fun pathParamSplit(
        @PathParam("parameter1") parameter1: String?,
        @PathParam("parameter2") parameter2: String?
): String {
    return (parameter1 ?: "") + "." +  (parameter2 ?: "")
}

リクエストパスから正規表現に対応した値を取り出す

パターンは正規表現で記述することもできます。
例)
@Path("/pathparam/regex/{regexMatched:.*}")
@PathParam("regexMatched") regexMatched: String?

http://localhost:8080/myapp/parameters/pathparam/regex/hogefuga/hoge

@GET
@Path("/pathparam/regex/{regexMatched:.*}")
@Produces(MediaType.TEXT_PLAIN)
fun pathParamRegex(@PathParam("regexMatched") regexMatched: String?): String {
    return if (regexMatched.isNullOrEmpty()) {
        "Not matched by regex."
    } else regexMatched
}

Formから送信されたリクエストを取得する

application/x-www-form-urlencoded 形式で送られたリクエストのパラメータは、
@FormParamで取り出すことができます。
例)
@FormParam("form1") form1param : String?
@FormParam("form2") form2param : String?

POSTなのでブラウザから直接たたけないため、CLI上で下記を叩いてください。
curl -d "form1=form1text" -d "form2=form2text" http://localhost:8080/myapp/parameters/formparam

@POST
@Path("/formparam")
@Produces(MediaType.TEXT_PLAIN)
fun formParam(
        @FormParam("form1") form1param : String?,
        @FormParam("form2") form2param : String?
): String {
    return buildString {
        appendln("form1 : $form1param")
        appendln("form2 : $form2param")
    }
}

ファイルアップロードに対応する

ほぼ以前書いた下記記事のままです。
【Kotlin/Java】Jersey2でファイルアップロードを扱う - B-Teck!

POSTなのでブラウザから直接たたけないため、CLI上で下記を叩いてください。
curl --header "Content-Type:application/octet-stream" -d "{"hoge": "fuga"}" http://localhost:8080/myapp/parameters/upload

@POST
@Path("/upload")
@Consumes(MediaType.APPLICATION_OCTET_STREAM)
@Produces(MediaType.TEXT_PLAIN)
fun upload(input : ByteArray): String {
    return input.toString(Charset.defaultCharset())
}

ここまでの作業

下記のタグまでが今回の記事の作業分です。
https://github.com/beatdjam/Jersey-On-Kotlin-Sample/tree/chapter2

作成したファイル
https://github.com/beatdjam/Jersey-On-Kotlin-Sample/blob/chapter2/src/main/kotlin/com/example/ParametersResource.kt
https://github.com/beatdjam/Jersey-On-Kotlin-Sample/blob/chapter2/src/main/kotlin/com/example/form/BeanParamSample.kt

【Kotlin】JerseyをKotlinで動かす その1 導入編

Jersey+KotlinでAPIサーバーを構築する方法と、実装例をご紹介していこうと思います。
とりあえず第一回目としてJersey公式の雛形からProjectを作成し、Kotlin化するところまでを書きます。

基本的な導入の手順は公式の Chapter 1. Getting Started を参考に書いていきます。

Jerseyの雛形を作る

  1. mavenのインストール
  2. 任意のフォルダの配下で下記コマンドを実行する
mvn archetype:generate -DarchetypeArtifactId=jersey-quickstart-grizzly2 \
-DarchetypeGroupId=org.glassfish.jersey.archetypes -DinteractiveMode=false \
-DgroupId=com.example -DartifactId=simple-service -Dpackage=com.example \
-DarchetypeVersion=2.28

これを実行するとフォルダ内に下記構成のディレクトリが生成されます。

simple-service
├── pom.xml
└── src
    ├── main
    │   └── java
    │       └── com
    │           └── example
    │               ├── Main.java
    │               └── MyResource.java
    └── test
        └── java
            └── com
                └── example
                    └── MyResourceTest.java

Kotlin化する

Add gitignore · beatdjam/Jersey-On-Kotlin-Sample@8296a0f · GitHub

pomの記述変更

Convert Java file to Kolin · beatdjam/Jersey-On-Kotlin-Sample@a64a0d7 · GitHub

主にやっていることは

  • dependenciesに下記を追記
    • kotlin-stdlib-jdk8
    • kotlin-test
  • buildに下記を追記
    • sourceDirectory
    • testSourceDirectory
  • pluginに下記を追記
    • kotlin-maven-plugin

ファイルのKotlin化

下記構成になるようファイルをKotlin化する。
IntelliJの機能でJavaからKotlinに変換したあと、記述をKotlinっぽくしたりした。
あと、 HttpServer.stop() がdeprecatedだったので HttpServer.shutdownNow() に書き換えたりした。

simple-service
├── pom.xml
└── src
    ├── main
    │   └── kotlin
    │       └── com
    │           └── example
    │               ├── Main.kt
    │               └── MyResource.kt
    └── test
        └── kotlin
            └── com
                └── example
                    └── MyResourceTest.kt

Main.java → Main.kt

Main.java
Main.kt

MyResource.java → MyResource.kt

MyResource.java
MyResource.kt

MyResourceTest.java → MyResourceTest.kt

MyResourceTest.java
MyResourceTest.kt

サーバーの起動

Jerseyの雛形にはGrizzlyという軽量なHttpServerが付属していて、下記コマンドで起動できます。

mvn clean test;mvn exec:java;

無事起動できたら、このような表示がCLI上に表示されます。

Jersey app started with WADL available at http://localhost:8080/myapp/ application.wadl
Hit enter to stop it...

http://localhost:8080/myapp/myresourceにアクセスして、 Got it! と表示されれば完了です。

Grizzly上ではなく通常のJavaEEアプリケーションとして動かす場合

通常のJavaEEアプリケーションとしてプロジェクトを作成する場合は、
1.4. Creating a JavaEE Web Application を参考にしてください。

ここまでの作業

下記のタグまでが今回の記事の作業分です。
https://github.com/beatdjam/Jersey-On-Kotlin-Sample/tree/chapter1