tapir adalah pustaka Scala untuk mendeskripsikan endpoint HTTP sebagai nilai bertipe. Satu definisi memuat metode, jalur, parameter, isi respons, dan bentuk errornya, sehingga compiler dapat memeriksa kesesuaian antara endpoint dan logika servernya. Topik ini membahas struktur tipe Endpoint, parameter query dan path, penulisan logika server dengan serverLogic, interpretasi definisi menjadi rute http4s, serta gagasan dokumentasi yang dihasilkan dari definisi yang sama. Sebagai contoh, dua endpoint perhitungan kecil dibangun, dijalankan pada port 18081, dan diuji dengan curl.
Endpoint sebagai nilai bertipe#
Definisi endpoint pada tapir adalah nilai dengan tipe Endpoint[A, I, E, O, R]. Lima parameter tipenya menyatakan input keamanan (A), input permintaan (I), error (E), output respons (O), dan kemampuan (R) yang menandai apakah definisi sudah dilengkapi logika server. Nilai endpoint bawaan menyediakan titik awal dengan seluruh parameter berupa Unit, sedangkan kombinator seperti get, in, out, dan errorOut mengisinya satu per satu.
val endpointCatatan: Endpoint[Unit, Int, String, String, Any] =
endpoint.get
.in("catatan")
.in(path[Int]("id"))
.out(stringBody)
.errorOut(stringBody)Kombinator in("catatan") menambahkan segmen statis pada jalur, sedangkan path[Int]("id") menangkap segmen berikutnya sebagai bilangan bulat. Kombinator out(stringBody) menyatakan bahwa respons berhasil berupa teks polos, dan errorOut(stringBody) menyatakan bahwa respons kegagalan juga berupa teks. Tipe hasilnya, yaitu Endpoint[Unit, Int, String, String, Any], mencatat semua keputusan itu. Pendekatan pencocokan pola HttpRoutes.of menyiratkan bentuk permintaan dan respons dari polanya, sedangkan definisi tapir menyatakannya secara eksplisit pada tipe, sehingga ketidaksesuaian terdeteksi saat kompilasi.
Parameter query dan path#
Parameter permintaan juga dinyatakan dengan kombinator in. query[T]("nama") mendeklarasikan parameter query bernama nama dengan tipe T, sedangkan path[T]("nama") mendeklarasikan segmen dinamis pada jalur. Tipe T harus memiliki codec, yaitu pasangan pembaca dan penulis nilai, dan untuk tipe bawaan seperti String, Int, serta Boolean codecnya sudah tersedia.
tapir mengekstrak dan mengonversi nilai-nilai tersebut dari permintaan sebelum logika server dijalankan. Apabila konversi gagal, misalnya segmen jalur tidak berupa angka, server langsung merespons dengan status 400 tanpa memanggil logika server. Dengan demikian, logika server hanya menerima nilai yang sudah valid bertipe T, dan penanganan dekode tidak perlu ditulis ulang di setiap endpoint.
Logika server dengan serverLogic#
Metode serverLogic menempelkan logika pada definisi endpoint. Fungsinya menerima nilai input dan menghasilkan efek yang berisi Either[E, O], yaitu nilai sisi kanan untuk keberhasilan dan nilai sisi kiri untuk kegagalan. tapir memetakan Right menjadi respons sesuai kombinator out, sedangkan Left menjadi respons sesuai kombinator errorOut, yaitu status 400 pada definisi yang errornya bertipe string.
//> using dep com.softwaremill.sttp.tapir::tapir-core:1.11.42
//> using dep com.softwaremill.sttp.tapir::tapir-http4s-server:1.11.42
//> using dep org.http4s::http4s-ember-server:0.23.38
//> using dep org.http4s::http4s-dsl:0.23.38
//> using dep org.slf4j:slf4j-nop:2.0.17
import cats.effect.IO
import cats.effect.IOApp
import cats.syntax.all.*
import com.comcast.ip4s.port
import org.http4s.ember.server.EmberServerBuilder
import sttp.tapir.*
import sttp.tapir.server.http4s.Http4sServerInterpreter
// endpoint dengan query parameter: GET /hitung?teks=...
val endpointHitung: Endpoint[Unit, String, String, String, Any] =
endpoint.get
.in("hitung")
.in(query[String]("teks"))
.out(stringBody)
.errorOut(stringBody)
// endpoint dengan path parameter: GET /kuadrat/{angka}
val endpointKuadrat: Endpoint[Unit, Int, String, String, Any] =
endpoint.get
.in("kuadrat")
.in(path[Int]("angka"))
.out(stringBody)
.errorOut(stringBody)
// logika server endpointHitung: menghitung jumlah kata
def hitungKata(teks: String): IO[Either[String, String]] =
val kata = teks.trim.split("\\s+").filter(_.nonEmpty)
if kata.isEmpty then IO.pure(Left("teks tidak boleh kosong"))
else IO.pure(Right(s"jumlah kata: ${kata.length}"))
// logika server endpointKuadrat: menghitung kuadrat bilangan
def kuadratkan(angka: Int): IO[Either[String, String]] =
IO.pure(Right(s"kuadrat dari $angka adalah ${angka * angka}"))
object ServerTapir extends IOApp.Simple:
def run: IO[Unit] =
val ruteHitung = Http4sServerInterpreter[IO]().toRoutes(
endpointHitung.serverLogic(hitungKata)
)
val ruteKuadrat = Http4sServerInterpreter[IO]().toRoutes(
endpointKuadrat.serverLogic(kuadratkan)
)
EmberServerBuilder
.default[IO]
.withPort(port"18081")
.withHttpApp((ruteHitung <+> ruteKuadrat).orNotFound)
.build
.use(_ => IO.never)Berkas di atas memuat dua endpoint beserta logikanya. hitungKata menghitung jumlah kata dan mengembalikan Left("teks tidak boleh kosong") apabila tidak ada kata yang bisa dihitung, sedangkan kuadratkan selalu berhasil dan selalu mengembalikan Right. Pemisahan ini menjaga dua jenis kegagalan tetap berbeda, yaitu kegagalan dekode yang ditangani tapir secara otomatis dan kegagalan bisnis yang diputuskan logika server melalui Left.
Interpretasi ke server http4s#
Definisi yang sudah memiliki logika menjadi layanan HTTP melalui interpreter http4s. Pemanggilan Http4sServerInterpreter[IO]().toRoutes(...) menghasilkan nilai HttpRoutes[IO], tipe yang sama dengan rute pada topik Dasar http4s. Beberapa kumpulan rute digabung dengan operator <+>, dilengkapi orNotFound, kemudian disambungkan ke server ember melalui EmberServerBuilder pada port 18081. Direktif //> using dep di bagian atas berkas digunakan scala-cli untuk mengunduh tapir, server ember, serta slf4j-nop yang meredam log agar keluaran server bersih.
Cara menjalankannya sama seperti pada layanan http4s biasa, yaitu scala-cli run server-tapir.scala pada folder berkasnya, kemudian dihentikan dengan menekan Ctrl+C.
Menguji endpoint dengan curl#
Empat permintaan berikut mencakup keberhasilan kedua endpoint, kegagalan logika server, dan kegagalan dekode. Jalankan server terlebih dahulu, lalu panggil endpoint dari terminal lain.
# tidak-dijalankan
# permintaan berhasil pada endpoint query dan path
curl -s "http://localhost:18081/hitung?teks=belajar%20scala%20bertipe"
curl -s "http://localhost:18081/kuadrat/12"
# permintaan yang gagal pada logika server dan pada dekode
curl -s "http://localhost:18081/hitung?teks="
curl -s "http://localhost:18081/kuadrat/abc"jumlah kata: 3
kuadrat dari 12 adalah 144
teks tidak boleh kosong
Invalid value for: path parameter angkaDua permintaan pertama menghasilkan status 200 dengan isi dari Right. Permintaan ketiga berhasil melewati dekode, tetapi hitungKata mengembalikan Left, sehingga tapir menjawab dengan status 400 yang berisi pesan errornya. Permintaan keempat gagal pada dekode karena abc tidak dapat dibaca sebagai Int, sehingga tapir menjawab dengan status 400 beserta pesan Invalid value for: path parameter angka sebelum logika server terpanggil. Opsi curl -i menampilkan status setiap respons apabila statusnya perlu diperiksa.
Kedua pesan error pada keluaran berasal dari sumber yang berbeda. Pesan teks tidak boleh kosong ditulis langsung pada logika server, sedangkan pesan Invalid value for: path parameter angka dihasilkan oleh bagian dekode tapir. Pemisahan itu memudahkan diagnosis, karena kegagalan dekode menunjukkan permintaan yang salah bentuk, sedangkan Left menunjukkan permintaan yang sah tetapi tidak dapat diproses.
Dokumentasi dari definisi yang sama#
Karena definisi endpoint adalah data yang dapat dibaca kembali, tapir dapat menghasilkan dokumentasi dari definisi yang sama dengan yang digunakan server. Modul tapir-openapi-docs memproses sekumpulan definisi menjadi nilai OpenAPI yang dapat dirender sebagai dokumen YAML, sedangkan modul tapir-swagger-ui menyajikan antarmuka Swagger UI yang membaca dokumen tersebut. Karena keduanya membaca definisi yang sama dengan yang digunakan server, jalur, parameter, dan bentuk error pada dokumentasi tidak mungkin menyimpang dari perilaku server. Apabila endpoint berubah, dokumentasi berubah pada kompilasi berikutnya tanpa penyuntingan terpisah. Dokumen semacam itu juga berguna sebagai kontrak bagi klien lain, karena daftar parameter dan bentuk responsnya terbaca tanpa membaca kode server.
Ringkasan#
- Definisi endpoint tapir adalah nilai bertipe
Endpoint[A, I, E, O, R]yang dibangun dengan kombinatorget,in,out, danerrorOut. query[T]danpath[T]mendeklarasikan parameter permintaan, dekodenya dilakukan otomatis, dan dekode yang gagal langsung dijawab dengan status 400.serverLogicmenjalankan logika server melaluiEither[E, O], yaituRightmenjadi respons berhasil danLeftmenjadi respons error sesuaierrorOut.Http4sServerInterpretermengubah definisi menjadiHttpRoutes, dan modultapir-openapi-docssertatapir-swagger-uimenghasilkan dokumentasi OpenAPI dari definisi yang sama.
Referensi#
- Endpoint — tapir by SoftwareMill — Dokumentasi struktur endpoint tapir beserta kombinator input, output, dan error.
- Server http4s — tapir by SoftwareMill
— Dokumentasi interpreter tapir untuk http4s, termasuk
Http4sServerInterpreterdantoRoutes. - OpenAPI documentation — tapir by SoftwareMill — Dokumentasi penghasilan dokumen OpenAPI dan Swagger UI dari definisi endpoint.