Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for http4s-stir based on kebs-akka-http #306

Merged
merged 2 commits into from
Aug 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading