Datatypes integrations

Cats datatypes integration

The tapir-cats module contains additional instances for some cats datatypes as well as additional syntax:

"com.softwaremill.sttp.tapir" %% "tapir-cats" % "0.18.0-M15"
  • import sttp.tapir.integ.cats.codec._ - brings schema, validator and codec instances
  • import sttp.tapir.integ.cats.syntax._ - brings additional syntax for tapir types

Refined integration

If you use refined, the tapir-refined module will provide implicit codecs and validators for T Refined P as long as a codec for T already exists:

"com.softwaremill.sttp.tapir" %% "tapir-refined" % "0.18.0-M15"

You’ll need to extend the sttp.tapir.codec.refined.TapirCodecRefined trait or import sttp.tapir.codec.refined._ to bring the implicit values into scope.

The refined codecs contain a validator which wrap/unwrap the value from/to its refined equivalent.

Some predicates will bind correctly to the vanilla tapir Validator, while others will bind to a custom validator that might not be very clear when reading the generated documentation. Correctly bound predicates can be found in integration/refined/src/main/scala/sttp/tapir/codec/refined/TapirCodecRefined.scala. If you are not satisfied with the validator generated by tapir-refined, you can provide an implicit ValidatorForPredicate[T, P] in scope using `ValidatorForPredicate.fromPrimitiveValidator’ to build it (do not hesitate to contribute your work!).

Enumeratum integration

The tapir-enumeratum module provides schemas, validators and codecs for Enumeratum enumerations. To use, add the following dependency:

"com.softwaremill.sttp.tapir" %% "tapir-enumeratum" % "0.18.0-M15"

Then, import sttp.tapir.codec.enumeratum, or extends the sttp.tapir.codec.enumeratum.TapirCodecEnumeratum trait.

This will bring into scope implicit values for values extending *EnumEntry.

Enumeration integration

There is no library for the use of the build in scala Enumeration, but it can be implemented by hand.

The example code below will generate enums to the open-api documentation.

import sttp.tapir._

trait EnumHelper {
  e: Enumeration =>

  import io.circe._

  implicit val enumDecoder: Decoder[e.Value] = Decoder.decodeEnumeration(e)
  implicit val enumEncoder: Encoder[e.Value] = Encoder.encodeEnumeration(e)

  // needs to be a def or lazy val so that the enumeration values are available!
  implicit def schemaForEnum: Schema[e.Value] = Schema.string.validate(Validator.enumeration(e.values.toList, v => Option(v)))
}

object Color extends Enumeration with EnumHelper {
  type Color = Value
  val Blue = Value("blue")
  val Red = Value("red")
}

NewType integration

If you use scala-newtype, the tapir-newtype module will provide implicit codecs and schemas for a types with a @newtype and @newsubtype annotations as long as a codec and schema for its underlying value already exists:

"com.softwaremill.sttp.tapir" %% "tapir-newtype" % "0.18.0-M15"

Then, import sttp.tapir.codec.newtype._, or extend the sttp.tapir.codec.enumeratum.TapirCodecNewType trait to bring the implicit values into scope.

Derevo integration

The tapir-derevo module provides a way to derive schema for your type using @derive annotation. For details refer to derevo documentation. To use, add the following dependency:

"com.softwaremill.sttp.tapir" %% "tapir-derevo" % "0.18.0-M15"

Then you can derive schema for your ADT along with other typeclasses besides ADT declaration itself:

import derevo.derive
import sttp.tapir.derevo.schema

@derive(schema)
case class Person(name: String, age: Int)

//or with custom description

@derive(schema("Type of currency in the country"))
sealed trait Currency
object Currency {
    case object CommunisticCurrency extends Currency
    case class USD(amount: Long) extends Currency
}

The annotation will simply generate a Schema[T] for your type T and put it into companion object. Generation rules are the same as in Schema.derived[T].

This will also work for newtypes — estatico or supertagged:

import derevo.derive
import sttp.tapir.derevo.schema
import io.estatico.newtype.macros.newtype

object types {
  
  @derive(schema)
  @newtype
  case class Amount(i: Int)
}

Resulting schema will be equivalent to implicitly[Schema[Int]].map(i => Some(types.Amount(i))). Note that due to limitations of the derevo library one can’t provide custom description for generated schema.