Skip to content

Commit

Permalink
Merge pull request #306 from theiterators/feature/http4s-stir
Browse files Browse the repository at this point in the history
Support for http4s-stir based on kebs-akka-http
  • Loading branch information
pk044 committed Aug 22, 2023
2 parents 0edfeed + d2c53c4 commit a045890
Show file tree
Hide file tree
Showing 16 changed files with 931 additions and 0 deletions.
27 changes: 27 additions & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,10 @@ def akkaHttpInExamples = {
val http4sVersion = "0.23.23"
val http4s = "org.http4s" %% "http4s-dsl" % http4sVersion

val http4sStirVersion = "0.2"
val http4sStir = "pl.iterators" %% "http4s-stir" % http4sStirVersion
val http4sStirTestkit = "pl.iterators" %% "http4s-stir-testkit" % http4sStirVersion

def akkaHttpInBenchmarks = akkaHttpInExamples :+ (akkaHttpTestkit).cross(CrossVersion.for3Use2_13)

lazy val commonSettings = baseSettings ++ Seq(
Expand Down Expand Up @@ -217,6 +221,15 @@ lazy val http4sSettings = commonSettings ++ Seq(
scalacOptions ++= paradiseFlag(scalaVersion.value)
)

lazy val http4sStirSettings = commonSettings ++ Seq(
libraryDependencies += http4s,
libraryDependencies += http4sStir,
libraryDependencies += http4sStirTestkit % "test",
libraryDependencies += optionalEnumeratum.cross(CrossVersion.for3Use2_13),
libraryDependencies ++= paradisePlugin(scalaVersion.value),
scalacOptions ++= paradiseFlag(scalaVersion.value)
)

lazy val jsonschemaSettings = commonSettings ++ Seq(
libraryDependencies += jsonschema.cross(CrossVersion.for3Use2_13)
)
Expand Down Expand Up @@ -388,6 +401,19 @@ lazy val http4sSupport = project
crossScalaVersions := supportedScalaVersions
)


lazy val http4sStirSupport = project
.in(file("http4s-stir"))
.dependsOn(core.jvm, instances, opaque.jvm % "test -> test", tagged.jvm % "test -> test", taggedMeta % "test -> test")
.settings(http4sStirSettings: _*)
.settings(publishSettings: _*)
.settings(
name := "http4s-stir",
description := "Automatic generation of http4s-stir deserializers for 1-element case classes, opaque and tagged types",
moduleName := "kebs-http4s-stir",
crossScalaVersions := supportedScalaVersions
).settings(disableScala(List("2.12")))

lazy val jsonschemaSupport = project
.in(file("jsonschema"))
.dependsOn(core.jvm)
Expand Down Expand Up @@ -517,6 +543,7 @@ lazy val kebs = project
scalacheckSupport,
akkaHttpSupport,
http4sSupport,
http4sStirSupport,
taggedMeta,
instances
)
Expand Down
26 changes: 26 additions & 0 deletions http4s-stir/src/main/scala-2/matchers/KebsMatchers.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package pl.iterators.kebs.matchers

import pl.iterators.stir.server.PathMatcher1
import enumeratum.{Enum, EnumEntry}
import pl.iterators.kebs.instances.InstanceConverter
import pl.iterators.kebs.macros.CaseClass1Rep

import scala.language.implicitConversions

trait KebsMatchers extends pl.iterators.stir.server.PathMatchers {

implicit class SegmentIsomorphism[U](segment: PathMatcher1[U]) {
def as[T](implicit rep: CaseClass1Rep[T, U]): PathMatcher1[T] = segment.map(rep.apply)
}

implicit class SegmentConversion[Source](segment: PathMatcher1[Source]) {
def to[Type](implicit ico: InstanceConverter[Type, Source]): PathMatcher1[Type] = segment.map(ico.decode)
}

object EnumSegment {
def as[T <: EnumEntry: Enum]: PathMatcher1[T] = {
val enumCompanion = implicitly[Enum[T]]
Segment.map(enumCompanion.withNameInsensitive)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package pl.iterators.kebs.unmarshallers.enums

import pl.iterators.stir.unmarshalling.PredefinedFromStringUnmarshallers._
import pl.iterators.stir.unmarshalling.{FromStringUnmarshaller, Unmarshaller}
import cats.effect.IO
import enumeratum.values._
import enumeratum.{Enum, EnumEntry}
import pl.iterators.kebs.macros.enums.{EnumOf, ValueEnumOf}

trait EnumUnmarshallers {
final def enumUnmarshaller[E <: EnumEntry](`enum`: Enum[E]): FromStringUnmarshaller[E] = Unmarshaller { name =>
`enum`.withNameInsensitiveOption(name) match {
case Some(enumEntry) => IO.pure(enumEntry)
case None =>
IO.raiseError(new IllegalArgumentException(s"""Invalid value '$name'. Expected one of: ${`enum`.namesToValuesMap.keysIterator
.mkString(", ")}"""))
}
}

implicit def kebsEnumUnmarshaller[E <: EnumEntry](implicit ev: EnumOf[E]): FromStringUnmarshaller[E] =
enumUnmarshaller(ev.`enum`)
}

trait ValueEnumUnmarshallers {
final def valueEnumUnmarshaller[V, E <: ValueEnumEntry[V]](`enum`: ValueEnum[V, E]): Unmarshaller[V, E] = Unmarshaller { v =>
`enum`.withValueOpt(v) match {
case Some(enumEntry) => IO.pure(enumEntry)
case None =>
IO.raiseError(new IllegalArgumentException(s"""Invalid value '$v'. Expected one of: ${`enum`.valuesToEntriesMap.keysIterator
.mkString(", ")}"""))
}
}

implicit def kebsValueEnumUnmarshaller[V, E <: ValueEnumEntry[V]](implicit ev: ValueEnumOf[V, E]): Unmarshaller[V, E] =
valueEnumUnmarshaller(ev.valueEnum)

implicit def kebsIntValueEnumFromStringUnmarshaller[E <: IntEnumEntry](implicit ev: ValueEnumOf[Int, E]): FromStringUnmarshaller[E] =
intFromStringUnmarshaller andThen valueEnumUnmarshaller(ev.valueEnum)
implicit def kebsLongValueEnumFromStringUnmarshaller[E <: LongEnumEntry](implicit ev: ValueEnumOf[Long, E]): FromStringUnmarshaller[E] =
longFromStringUnmarshaller andThen valueEnumUnmarshaller(ev.valueEnum)
implicit def kebsShortValueEnumFromStringUnmarshaller[E <: ShortEnumEntry](
implicit ev: ValueEnumOf[Short, E]): FromStringUnmarshaller[E] =
shortFromStringUnmarshaller andThen valueEnumUnmarshaller(ev.valueEnum)
implicit def kebsByteValueEnumFromStringUnmarshaller[E <: ByteEnumEntry](implicit ev: ValueEnumOf[Byte, E]): FromStringUnmarshaller[E] =
byteFromStringUnmarshaller andThen valueEnumUnmarshaller(ev.valueEnum)
}

trait KebsEnumUnmarshallers extends EnumUnmarshallers with ValueEnumUnmarshallers {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package pl.iterators.kebs.unmarshallers

package object enums extends KebsEnumUnmarshallers
26 changes: 26 additions & 0 deletions http4s-stir/src/main/scala-3/matchers/KebsMatchers.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package pl.iterators.kebs.matchers

import pl.iterators.stir.server.PathMatcher1
import pl.iterators.kebs.instances.InstanceConverter
import pl.iterators.kebs.macros.CaseClass1Rep
import pl.iterators.kebs.macros.enums.EnumOf
import scala.reflect.Enum

import scala.language.implicitConversions

trait KebsMatchers extends pl.iterators.stir.server.PathMatchers {

implicit class SegmentIsomorphism[U](segment: PathMatcher1[U]) {
def as[T](implicit rep: CaseClass1Rep[T, U]): PathMatcher1[T] = segment.map(rep.apply)
}

implicit class SegmentConversion[Source](segment: PathMatcher1[Source]) {
def to[Type](implicit ico: InstanceConverter[Type, Source]): PathMatcher1[Type] = segment.map(ico.decode)
}

object EnumSegment {
def as[T <: Enum](using e: EnumOf[T]): PathMatcher1[T] = {
Segment.map(s => e.`enum`.values.find(_.toString().toLowerCase() == s.toLowerCase()).getOrElse(throw new IllegalArgumentException(s"""Invalid value '$s'. Expected one of: ${e.`enum`.values.mkString(", ")}""")))
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package pl.iterators.kebs.unmarshallers.enums

import pl.iterators.stir.unmarshalling.PredefinedFromStringUnmarshallers._
import pl.iterators.stir.unmarshalling.{FromStringUnmarshaller, Unmarshaller}
import cats.effect.IO
import pl.iterators.kebs.macros.enums.{EnumOf, ValueEnumOf}
import pl.iterators.kebs.enums.ValueEnum
import scala.reflect.Enum
import scala.reflect.ClassTag

trait EnumUnmarshallers {
final def enumUnmarshaller[E <: Enum](using e: EnumOf[E]): FromStringUnmarshaller[E] = Unmarshaller { name =>
e.`enum`.values.find(_.toString().toLowerCase() == name.toLowerCase()) match {
case Some(enumEntry) => IO.pure(enumEntry)
case None =>
IO.raiseError(new IllegalArgumentException(s"""Invalid value '$name'. Expected one of: ${e.`enum`.values.mkString(", ")}"""))
}
}

given kebsEnumUnmarshaller[E <: Enum](using e: EnumOf[E]): FromStringUnmarshaller[E] =
enumUnmarshaller
}

trait ValueEnumUnmarshallers extends EnumUnmarshallers {
final def valueEnumUnmarshaller[V, E <: ValueEnum[V] with Enum](using `enum`: ValueEnumOf[V, E], cls: ClassTag[V]): Unmarshaller[V, E] = Unmarshaller { v =>
`enum`.`enum`.values.find(e => e.value == v) match {
case Some(enumEntry) => IO.pure(enumEntry)
case None =>
IO.raiseError(new IllegalArgumentException(s"""Invalid value '$v'. Expected one of: ${`enum`.`enum`.values.map(_.value).mkString(", ")}"""))
}
}

given kebsValueEnumUnmarshaller[V, E <: ValueEnum[V] with Enum](using `enum`: ValueEnumOf[V, E], cls: ClassTag[V]): Unmarshaller[V, E] =
valueEnumUnmarshaller

given kebsIntValueEnumFromStringUnmarshaller[E <: ValueEnum[Int] with Enum](using ev: ValueEnumOf[Int, E]): FromStringUnmarshaller[E] =
intFromStringUnmarshaller andThen valueEnumUnmarshaller
given kebsLongValueEnumFromStringUnmarshaller[E <: ValueEnum[Long] with Enum](using ev: ValueEnumOf[Long, E]): FromStringUnmarshaller[E] =
longFromStringUnmarshaller andThen valueEnumUnmarshaller
given kebsShortValueEnumFromStringUnmarshaller[E <: ValueEnum[Short] with Enum](
using ev: ValueEnumOf[Short, E]): FromStringUnmarshaller[E] =
shortFromStringUnmarshaller andThen valueEnumUnmarshaller
given kebsByteValueEnumFromStringUnmarshaller[E <: ValueEnum[Byte] with Enum](using ev: ValueEnumOf[Byte, E]): FromStringUnmarshaller[E] =
byteFromStringUnmarshaller andThen valueEnumUnmarshaller
}

trait KebsEnumUnmarshallers extends ValueEnumUnmarshallers {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package pl.iterators.kebs.unmarshallers

package object enums extends KebsEnumUnmarshallers
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package pl.iterators.kebs

package object matchers extends KebsMatchers
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package pl.iterators.kebs.unmarshallers

import pl.iterators.stir.unmarshalling.{FromStringUnmarshaller, Unmarshaller}
import pl.iterators.kebs.instances.InstanceConverter
import pl.iterators.kebs.macros.CaseClass1Rep

trait KebsUnmarshallers extends LowPriorityKebsUnmarshallers {
@inline
implicit def kebsFromStringUnmarshaller[A, B](implicit rep: CaseClass1Rep[B, A],
fsu: FromStringUnmarshaller[A]): FromStringUnmarshaller[B] =
fsu andThen kebsUnmarshaller(rep)


@inline
implicit def kebsInstancesFromStringUnmarshaller[A, B](implicit ico: InstanceConverter[B, A],
fsu: FromStringUnmarshaller[A]): FromStringUnmarshaller[B] =
fsu andThen kebsInstancesUnmarshaller(ico)

}

trait LowPriorityKebsUnmarshallers {
implicit def kebsInstancesUnmarshaller[A, B](implicit ico: InstanceConverter[B, A]): Unmarshaller[A, B] =
Unmarshaller.strict[A, B](ico.decode)

implicit def kebsUnmarshaller[A, B](implicit rep: CaseClass1Rep[B, A]): Unmarshaller[A, B] =
Unmarshaller.strict[A, B](rep.apply)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package pl.iterators.kebs

package object unmarshallers extends KebsUnmarshallers
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package pl.iterators.kebs

import enumeratum.values.{IntEnum, IntEnumEntry, StringEnum, StringEnumEntry}
import enumeratum.{Enum, EnumEntry}
import pl.iterators.kebs.tag.meta.tagged
import pl.iterators.kebs.tagged._

import java.net.URI
import java.util.UUID

@tagged trait Tags {
trait IdTag
type Id = Long @@ IdTag

trait TestIdTag
type TestId = UUIDId @@ TestIdTag

trait TestDoubleTag
type TestDouble = Double @@ TestDoubleTag

type UUIDId = UUID
object UUIDId {
def generate[T]: UUIDId @@ T = UUID.randomUUID().taggedWith[T]
def fromString[T](str: String): UUIDId @@ T =
UUID.fromString(str).taggedWith[T]
}

trait TestTaggedUriTag
type TestTaggedUri = URI @@ TestTaggedUriTag

}

object Domain extends Tags {
case class I(i: Int)
case class S(s: String)
case class P[A](a: A)
case class CantUnmarshall(s: String, i: Int)
case object O

sealed trait Greeting extends EnumEntry
object Greeting extends Enum[Greeting] {
case object Hello extends Greeting
case object GoodBye extends Greeting
case object Hi extends Greeting
case object Bye extends Greeting

val values = findValues
}

sealed abstract class LibraryItem(val value: Int) extends IntEnumEntry

object LibraryItem extends IntEnum[LibraryItem] {
case object Book extends LibraryItem(1)
case object Movie extends LibraryItem(2)
case object Magazine extends LibraryItem(3)
case object CD extends LibraryItem(4)

val values = findValues
}

case class Red(value: Int)
case class Green(value: Int)
case class Blue(value: Int)
case class Color(red: Red, green: Green, blue: Blue)

sealed abstract class ShirtSize(val value: String) extends StringEnumEntry
object ShirtSize extends StringEnum[ShirtSize] {
case object Small extends ShirtSize("S")
case object Medium extends ShirtSize("M")
case object Large extends ShirtSize("L")

val values = findValues
}

sealed trait SortOrder extends EnumEntry
object SortOrder extends Enum[SortOrder] {
case object Asc extends SortOrder
case object Desc extends SortOrder

override val values = findValues
}

}
Loading

0 comments on commit a045890

Please sign in to comment.