Created
June 15, 2024 07:58
-
-
Save dacr/8ec983f3dc90da617dd1c816eca760af to your computer and use it in GitHub Desktop.
tapir with refined for data validity and better documentation - simple way to build refined instances ? / published by https://github.com/dacr/code-examples-manager #f2e2cb9b-7a0b-4bf9-93c9-8c5596ab50b7/791d7ef2ff2d31b7eb30520d54b22e765f7518df
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// summary : tapir with refined for data validity and better documentation - simple way to build refined instances ? | |
// keywords : scala, zio, tapir, refined, @testable, @exclusive | |
// publish : gist | |
// authors : David Crosson | |
// license : Apache NON-AI License Version 2.0 (https://raw.githubusercontent.com/non-ai-licenses/non-ai-licenses/main/NON-AI-APACHE2) | |
// id : f2e2cb9b-7a0b-4bf9-93c9-8c5596ab50b7 | |
// created-on : 2024-06-06T11:11:45+02:00 | |
// managed-by : https://github.com/dacr/code-examples-manager | |
// run-with : scala-cli $file | |
// test-with : curl -L http://127.0.0.1:8080/hello/david | |
// --------------------- | |
//> using scala "3.4.2" | |
//> using dep "com.softwaremill.sttp.tapir::tapir-zio:1.10.9" | |
//> using dep "com.softwaremill.sttp.tapir::tapir-zio-http-server:1.10.9" | |
//> using dep "com.softwaremill.sttp.tapir::tapir-refined:1.10.9" | |
//> using dep "com.softwaremill.sttp.tapir::tapir-json-zio:1.10.9" | |
//> using dep "com.softwaremill.sttp.tapir::tapir-swagger-ui-bundle:1.10.9" | |
//> using dep "dev.zio::zio-json-interop-refined:0.7.0" | |
// --------------------- | |
import sttp.tapir.ztapir.* | |
import sttp.tapir.server.ziohttp.ZioHttpInterpreter | |
import sttp.apispec.openapi.Info | |
import sttp.tapir.swagger.bundle.SwaggerInterpreter | |
import sttp.tapir.Schema.annotations.customise | |
import sttp.tapir.json.zio.* | |
import sttp.tapir.Schema | |
import sttp.tapir.generic.auto.* | |
import sttp.tapir.codec.refined.* | |
import eu.timepit.refined.* | |
import eu.timepit.refined.auto.* | |
import eu.timepit.refined.numeric.* | |
import eu.timepit.refined.api.* | |
import eu.timepit.refined.boolean.* | |
import eu.timepit.refined.collection.* | |
import eu.timepit.refined.string.* | |
import eu.timepit.refined.generic.* | |
import zio.* | |
import zio.json.* | |
import zio.json.interop.refined.* | |
import zio.http.Server | |
object WebApp extends ZIOAppDefault { | |
enum Gender derives JsonCodec { | |
case Female, Male | |
} | |
object Gender { | |
given JsonEncoder[Gender] = JsonEncoder[String].contramap(p => p.toString) | |
given JsonDecoder[Gender] = JsonDecoder[String].map(p => Gender.valueOf(p)) | |
given Schema[Gender] = Schema.derivedEnumeration.defaultStringBased | |
} | |
type SaluteName = String Refined NonEmpty | |
object SaluteName extends RefinedTypeOps[SaluteName, String] | |
type Age = Int Refined Positive | |
object Age extends RefinedTypeOps.Numeric[Age, Int] | |
type NickName = String Refined (MinSize[3] And MaxSize[6]) | |
object NickName extends RefinedTypeOps[NickName, String] | |
type NickNames = List[NickName] Refined NonEmpty | |
object NickNames extends RefinedTypeOps[NickNames, List[NickName]] | |
case class Salute( | |
name: SaluteName, | |
age: Option[Age], | |
gender: Option[Gender], | |
nicknames: NickNames | |
) derives JsonCodec | |
case class Greeting(message: String) derives JsonCodec | |
// -------------------------------------------------- | |
// val age: Age = 42 // only possible using scala 2 :( Waiting for an update | |
// val name: SaluteName = "jonathan" // only possible using scala 2 :( Waiting for an update | |
// val nicknames: NickNames = List("joe") // only possible using scala 2 :( Waiting for an update | |
val age: Age = Age.unsafeFrom(42) | |
val name: SaluteName = SaluteName.unsafeFrom("jonathan") | |
val nickName: NickName = NickName.unsafeFrom("joe") | |
val nicknames: NickNames = NickNames.unsafeFrom(List(nickName)) | |
val saluteExample = | |
Salute( | |
name, | |
None, | |
None, | |
nicknames | |
) | |
val helloEndPoint = | |
endpoint | |
.tag("Greetings") | |
.name("hello") | |
.description("Returns stateful greeting") | |
.post | |
.in("hello") | |
.in(jsonBody[Salute].example(saluteExample)) | |
.out(jsonBody[Greeting]) | |
def helloLogic(salute: Salute): UIO[Greeting] = { | |
for { | |
nickName <- Random.shuffle(salute.nicknames.toList).map(_.headOption.map(_.value)) // .when(!salute.nicknames.isEmpty) | |
name = nickName.getOrElse(salute.name) | |
message = salute match { | |
case Salute(name, Some(foundAge), _, _) if foundAge < 18 => s"Hi $name" | |
case Salute(name, Some(foundAge), Some(Gender.Female), _) => s"Hello madam $name" | |
case Salute(name, Some(foundAge), Some(Gender.Male), _) => s"Hello mister $name" | |
case Salute(name, _, _, _) => s"Good morning $name" | |
} | |
} yield Greeting(message) | |
} | |
val helloRoute = helloEndPoint.zServerLogic[Any](helloLogic) | |
// -------------------------------------------------- | |
val apiDocRoute = | |
SwaggerInterpreter() | |
.fromServerEndpoints( | |
List(helloRoute), | |
Info( | |
title = "Greeting API", | |
version = "1.0", | |
description = Some("Everything required to be polite") | |
) | |
) | |
// -------------------------------------------------- | |
val routes = ZioHttpInterpreter().toHttp(List(helloRoute) ++ apiDocRoute) | |
// -------------------------------------------------- | |
val server = for { | |
_ <- ZIO.log("API documentation : http://127.0.0.1:8080/docs") | |
_ <- Server.serve(routes) | |
} yield () | |
override def run = server.provide(Server.default) | |
} | |
WebApp.main(Array.empty) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment