Skip to content

Commit

Permalink
Merge branch 'master' into jsoniter_codegen_support
Browse files Browse the repository at this point in the history
  • Loading branch information
kciesielski authored Mar 18, 2024
2 parents bc31530 + 2809a0f commit 14bd314
Show file tree
Hide file tree
Showing 34 changed files with 212 additions and 102 deletions.
18 changes: 15 additions & 3 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -256,13 +256,13 @@ lazy val allAggregates: Seq[ProjectReference] = {
}
if (sys.env.isDefinedAt("ONLY_LOOM")) {
println("[info] ONLY_LOOM defined, including only loom-based projects")
filteredByNative.filter(p => (p.toString.contains("Loom") || p.toString.contains("nima")))
filteredByNative.filter(p => (p.toString.contains("Loom") || p.toString.contains("nima") || p.toString.contains("perfTests")))
} else if (sys.env.isDefinedAt("ALSO_LOOM")) {
println("[info] ALSO_LOOM defined, including also loom-based projects")
filteredByNative
} else {
println("[info] ONLY_LOOM *not* defined, *not* including loom-based-projects")
filteredByNative.filterNot(p => (p.toString.contains("Loom") || p.toString.contains("nima")))
filteredByNative.filterNot(p => (p.toString.contains("Loom") || p.toString.contains("nima") || p.toString.contains("perfTests")))
}

}
Expand Down Expand Up @@ -538,7 +538,18 @@ lazy val perfTests: ProjectMatrix = (projectMatrix in file("perf-tests"))
Test / run / javaOptions --= perfServerJavaOptions
)
.jvmPlatform(scalaVersions = List(scala2_13))
.dependsOn(core, pekkoHttpServer, http4sServer, nettyServer, nettyServerCats, playServer, vertxServer, vertxServerCats)
.dependsOn(
core,
pekkoHttpServer,
http4sServer,
nettyServer,
nettyServerCats,
nettyServerLoom,
playServer,
vertxServer,
vertxServerCats,
nimaServer
)

// integrations

Expand Down Expand Up @@ -1422,6 +1433,7 @@ lazy val jdkhttpServer: ProjectMatrix = (projectMatrix in file("server/jdkhttp-s

lazy val nettyServer: ProjectMatrix = (projectMatrix in file("server/netty-server"))
.settings(commonJvmSettings)
.enablePlugins(BuildInfoPlugin)
.settings(
name := "tapir-netty-server",
libraryDependencies ++= Seq(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ object TupleOps {
trait BinaryPolyFunc {
def at[A, B] = new CaseBuilder[A, B]
class CaseBuilder[A, B] {
def apply[R](f: (A, B) R) = new BinaryPolyFunc.Case[A, B, BinaryPolyFunc.this.type] {
def apply[R](f: (A, B) => R) = new BinaryPolyFunc.Case[A, B, BinaryPolyFunc.this.type] {
type Out = R
def apply(a: A, b: B) = f(a, b)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,6 @@ trait SchemaMagnoliaDerivation {
}

override def split[T](ctx: SealedTrait[Schema, T]): Schema[T] = {
val annotations = mergeAnnotations(ctx.annotations, ctx.inheritedAnnotations)
withCache(ctx.typeInfo, ctx.annotations) {
val subtypesByName =
ctx.subtypes.toList
Expand Down
28 changes: 13 additions & 15 deletions core/src/main/scala-3/sttp/tapir/internal/AnnotationsMacros.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,12 @@ package sttp.tapir.internal

import sttp.tapir._
import sttp.tapir.CodecFormat.TextPlain
import sttp.tapir.internal.{CaseClass, CaseClassField}
import sttp.tapir.typelevel.ParamConcat
import sttp.model.{QueryParams, Header, StatusCode}
import sttp.model.headers.{Cookie, CookieWithMeta, CookieValueWithMeta}

import scala.collection.mutable
import scala.quoted.*
import scala.deriving.Mirror

private[tapir] class AnnotationsMacros[T <: Product: Type](using q: Quotes) {
import quotes.reflect.*
Expand Down Expand Up @@ -41,7 +39,7 @@ private[tapir] class AnnotationsMacros[T <: Product: Type](using q: Quotes) {
inputIdxToFieldIdx += (inputIdxToFieldIdx.size -> idx)
wrapInput[f](field, makePathInput[f](field))
case None =>
report.throwError(s"${caseClass.name}.${fieldName} must have the @path annotation, as it is referenced from @endpointInput.")
report.errorAndAbort(s"${caseClass.name}.${fieldName} must have the @path annotation, as it is referenced from @endpointInput.")
}
} else {
makeFixedPath(segment)
Expand All @@ -51,7 +49,7 @@ private[tapir] class AnnotationsMacros[T <: Product: Type](using q: Quotes) {
val (pathFields, nonPathFields) = fieldsWithIndex.partition((f, _) => f.annotated(pathAnnotationSymbol))

if (inputIdxToFieldIdx.size != pathFields.size) {
report.throwError(s"Not all fields of ${caseClass.name} annotated with @path are captured in the path in @endpointInput.")
report.errorAndAbort(s"Not all fields of ${caseClass.name} annotated with @path are captured in the path in @endpointInput.")
}

val nonPathInputs = nonPathFields.map { case (field, fieldIdx) =>
Expand All @@ -77,7 +75,7 @@ private[tapir] class AnnotationsMacros[T <: Product: Type](using q: Quotes) {
.map(makeBasicAuthInput[f](field, field.annotation(securitySchemeNameAnnotationSymbol), _))
)
.getOrElse {
report.throwError(
report.errorAndAbort(
s"All fields of ${caseClass.name} must be annotated with one of the annotations from sttp.tapir.annotations. No annotations for field: ${field.name}."
)
}
Expand Down Expand Up @@ -125,7 +123,7 @@ private[tapir] class AnnotationsMacros[T <: Product: Type](using q: Quotes) {
.orElse(if (field.annotated(cookiesAnnotationSymbol)) Some(makeCookiesIO[f](field)) else None)
.orElse(if (field.annotated(setCookiesAnnotationSymbol)) Some(makeSetCookiesOutput[f](field)) else None)
.getOrElse {
report.throwError(
report.errorAndAbort(
s"All fields of ${caseClass.name} must be annotated with one of the annotations from sttp.tapir.annotations. No annotations for field: ${field.name}."
)
}
Expand Down Expand Up @@ -190,14 +188,14 @@ private[tapir] class AnnotationsMacros[T <: Product: Type](using q: Quotes) {
// util
private def summonCodec[L: Type, H: Type, CF <: CodecFormat: Type](field: CaseClassField[q.type, T]): Expr[Codec[L, H, CF]] =
Expr.summon[Codec[L, H, CF]].getOrElse {
report.throwError(
report.errorAndAbort(
s"Cannot summon codec from: ${Type.show[L]}, to: ${Type.show[H]}, formatted as: ${Type.show[CF]}, for field: ${field.name}."
)
}

private def summonMultipartCodec[H: Type](field: CaseClassField[q.type, T]): Expr[MultipartCodec[H]] =
Expr.summon[MultipartCodec[H]].getOrElse {
report.throwError(
report.errorAndAbort(
s"Cannot summon multipart codec for type: ${Type.show[H]}, for field: ${field.name}."
)
}
Expand Down Expand Up @@ -245,20 +243,20 @@ private[tapir] class AnnotationsMacros[T <: Product: Type](using q: Quotes) {
private def makeQueryParamsInput[f: Type](field: CaseClassField[q.type, T]): Expr[EndpointInput.Basic[f]] = {
summon[Type[f]] match {
case '[QueryParams] => '{ queryParams.asInstanceOf[EndpointInput.Basic[f]] }
case _ => report.throwError(annotationErrorMsg[f, QueryParams]("@params", field))
case _ => report.errorAndAbort(annotationErrorMsg[f, QueryParams]("@params", field))
}
}

private def makeHeadersIO[f: Type](field: CaseClassField[q.type, T]): Expr[EndpointIO.Basic[f]] =
summon[Type[f]] match {
case '[List[Header]] => '{ headers.asInstanceOf[EndpointIO.Basic[f]] }
case _ => report.throwError(annotationErrorMsg[f, List[Header]]("@headers", field))
case _ => report.errorAndAbort(annotationErrorMsg[f, List[Header]]("@headers", field))
}

private def makeCookiesIO[f: Type](field: CaseClassField[q.type, T]): Expr[EndpointIO.Basic[f]] =
summon[Type[f]] match {
case '[List[Cookie]] => '{ cookies.asInstanceOf[EndpointIO.Basic[f]] }
case _ => report.throwError(annotationErrorMsg[f, List[Cookie]]("@cookies", field))
case _ => report.errorAndAbort(annotationErrorMsg[f, List[Cookie]]("@cookies", field))
}

private def makePathInput[f: Type](field: CaseClassField[q.type, T]): Expr[EndpointInput.Basic[f]] = {
Expand All @@ -271,22 +269,22 @@ private[tapir] class AnnotationsMacros[T <: Product: Type](using q: Quotes) {
private def makeStatusCodeOutput[f: Type](field: CaseClassField[q.type, T]): Expr[EndpointOutput.Basic[f]] = {
summon[Type[f]] match {
case '[StatusCode] => '{ statusCode.asInstanceOf[EndpointOutput.Basic[f]] }
case _ => report.throwError(annotationErrorMsg[f, StatusCode]("@statusCode", field))
case _ => report.errorAndAbort(annotationErrorMsg[f, StatusCode]("@statusCode", field))
}
}

private def makeSetCookieOutput[f: Type](field: CaseClassField[q.type, T])(altName: Option[String]): Expr[EndpointOutput.Basic[f]] = {
val name = Expr(altName.getOrElse(field.name))
summon[Type[f]] match {
case '[CookieValueWithMeta] => '{ setCookie($name).asInstanceOf[EndpointOutput.Basic[f]] }
case _ => report.throwError(annotationErrorMsg[f, CookieValueWithMeta]("@setCookie", field))
case _ => report.errorAndAbort(annotationErrorMsg[f, CookieValueWithMeta]("@setCookie", field))
}
}

private def makeSetCookiesOutput[f: Type](field: CaseClassField[q.type, T]): Expr[EndpointOutput.Basic[f]] = {
summon[Type[f]] match {
case '[List[CookieWithMeta]] => '{ setCookies.asInstanceOf[EndpointOutput.Basic[f]] }
case _ => report.throwError(annotationErrorMsg[f, List[CookieWithMeta]]("@setCookies", field))
case _ => report.errorAndAbort(annotationErrorMsg[f, List[CookieWithMeta]]("@setCookies", field))
}
}

Expand Down Expand Up @@ -420,7 +418,7 @@ private[tapir] class AnnotationsMacros[T <: Product: Type](using q: Quotes) {
case '[EndpointTransput.Atom[`f`]] =>
'{ $f($transput.asInstanceOf[EndpointTransput.Atom[f]]).asInstanceOf[EndpointTransput.Atom[f]] }
case t =>
report.throwError(
report.errorAndAbort(
s"Schema metadata can only be added to basic inputs/outputs, but got: ${Type.show(using t)}, on field: ${field.name}"
)
}
Expand Down
12 changes: 6 additions & 6 deletions core/src/main/scala-3/sttp/tapir/internal/CaseClass.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ private[tapir] class CaseClass[Q <: Quotes, T: Type](using val q: Q) {
val symbol = tpe.typeSymbol

if !symbol.flags.is(Flags.Case) then
report.throwError(s"CaseClass can be instantiated only for case classes, but got: ${summon[Type[T]]}")
report.errorAndAbort(s"CaseClass can be instantiated only for case classes, but got: ${summon[Type[T]]}")

def name = symbol.name

Expand Down Expand Up @@ -51,7 +51,7 @@ private[tapir] class CaseClass[Q <: Quotes, T: Type](using val q: Q) {
symbol.getAnnotation(annSymbol).map {
case Apply(_, List(Select(_, "$lessinit$greater$default$1"))) => None
case Apply(_, List(Literal(c: Constant))) if c.value.isInstanceOf[String] => Some(c.value.asInstanceOf[String])
case _ => report.throwError(s"Cannot extract annotation: @${annSymbol.name}, from class: ${symbol.name}")
case _ => report.errorAndAbort(s"Cannot extract annotation: @${annSymbol.name}, from class: ${symbol.name}")
}
}
}
Expand All @@ -70,26 +70,26 @@ private[tapir] class CaseClassField[Q <: Quotes, T](using val q: Q, t: Type[T])(
/** Extracts an argument from an annotation with a single string-valued argument. */
def extractStringArgFromAnnotation(annSymbol: Symbol): Option[String] = constructorField.getAnnotation(annSymbol).map {
case Apply(_, List(Literal(c: Constant))) if c.value.isInstanceOf[String] => c.value.asInstanceOf[String]
case _ => report.throwError(s"Cannot extract annotation: @${annSymbol.name}, from field: ${symbol.name}, of type: ${Type.show[T]}")
case _ => report.errorAndAbort(s"Cannot extract annotation: @${annSymbol.name}, from field: ${symbol.name}, of type: ${Type.show[T]}")
}

/** Extracts an optional argument from an annotation with a single string-valued argument with a default value. */
def extractOptStringArgFromAnnotation(annSymbol: Symbol): Option[Option[String]] = {
constructorField.getAnnotation(annSymbol).map {
case Apply(_, List(Select(_, "$lessinit$greater$default$1"))) => None
case Apply(_, List(Literal(c: Constant))) if c.value.isInstanceOf[String] => Some(c.value.asInstanceOf[String])
case _ => report.throwError(s"Cannot extract annotation: @${annSymbol.name}, from field: ${symbol.name}, of type: ${Type.show[T]}")
case _ => report.errorAndAbort(s"Cannot extract annotation: @${annSymbol.name}, from field: ${symbol.name}, of type: ${Type.show[T]}")
}
}

def extractTreeFromAnnotation(annSymbol: Symbol): Option[Tree] = constructorField.getAnnotation(annSymbol).map {
case Apply(_, List(t)) => t
case _ => report.throwError(s"Cannot extract annotation: @${annSymbol.name}, from field: ${symbol.name}, of type: ${Type.show[T]}")
case _ => report.errorAndAbort(s"Cannot extract annotation: @${annSymbol.name}, from field: ${symbol.name}, of type: ${Type.show[T]}")
}

def extractFirstTreeArgFromAnnotation(annSymbol: Symbol): Option[Tree] = constructorField.getAnnotation(annSymbol).map {
case Apply(_, List(t, _*)) => t
case _ => report.throwError(s"Cannot extract annotation: @${annSymbol.name}, from field: ${symbol.name}, of type: ${Type.show[T]}")
case _ => report.errorAndAbort(s"Cannot extract annotation: @${annSymbol.name}, from field: ${symbol.name}, of type: ${Type.show[T]}")
}

def annotated(annSymbol: Symbol): Boolean = annotation(annSymbol).isDefined
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,17 @@ package sttp.tapir.internal

import scala.quoted.*

import scala.deriving.Mirror

private[tapir] object ComplietimeErrors {
inline def reportIncorrectMapping[SOURCE, TARGET] = ${ reportIncorrectMappingImpl[SOURCE, TARGET] }

private def reportIncorrectMappingImpl[SOURCE: Type, TARGET: Type](using Quotes): Expr[Unit] = {
import quotes.reflect.*

if TypeRepr.of[TARGET].typeSymbol.declaredFields.size > 22 then
report.throwError(
report.errorAndAbort(
s"Cannot map to ${Type.show[TARGET]}: arities of up to 22 are supported. If you need more inputs/outputs, map them to classes with less fields, and then combine these classes."
)
else report.throwError(s"Failed to map ${Type.show[SOURCE]} into ${Type.show[TARGET]}")
else report.errorAndAbort(s"Failed to map ${Type.show[SOURCE]} into ${Type.show[TARGET]}")
}

}
3 changes: 1 addition & 2 deletions core/src/main/scala-3/sttp/tapir/macros/CodecMacros.scala
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
package sttp.tapir.macros

import sttp.tapir.Codec.PlainCodec
import sttp.tapir.CodecFormat.TextPlain
import sttp.tapir.{Codec, DecodeResult, Schema, SchemaAnnotations, Validator}
import sttp.tapir.{Codec, SchemaAnnotations, Validator}
import sttp.tapir.internal.CodecValueClassMacro

trait CodecMacros {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import sttp.tapir.EndpointInput
import sttp.tapir.internal.AnnotationsMacros

import scala.quoted.*
import scala.deriving.Mirror

trait EndpointInputMacros {

Expand Down
3 changes: 1 addition & 2 deletions core/src/main/scala-3/sttp/tapir/macros/EndpointMacros.scala
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
package sttp.tapir.macros

import sttp.tapir.{EndpointErrorOutputsOps, EndpointSecurityInputsOps, EndpointInputsOps, EndpointOutputsOps, EndpointTransput, Mapping}
import sttp.tapir.{EndpointErrorOutputsOps, EndpointSecurityInputsOps, EndpointInputsOps, EndpointOutputsOps}
import sttp.tapir.internal.MappingMacros

import scala.compiletime.erasedValue
import scala.deriving.Mirror

trait EndpointSecurityInputsMacros[A, I, E, O, -R] { this: EndpointSecurityInputsOps[A, I, E, O, R] =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import sttp.tapir.EndpointOutput
import sttp.tapir.internal.AnnotationsMacros

import scala.quoted.*
import scala.deriving.Mirror

trait EndpointOutputMacros {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package sttp.tapir.macros
import sttp.tapir.EndpointTransput
import sttp.tapir.internal.MappingMacros

import scala.compiletime.erasedValue
import scala.deriving.Mirror

trait EndpointTransputMacros[T] { this: EndpointTransput[T] =>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
package sttp.tapir.macros

import sttp.model.StatusCode
import sttp.tapir.EndpointOutput.OneOfVariant
import sttp.tapir.{EndpointOutput, Tapir}
import sttp.tapir.typelevel.ErasureSameAsType

import scala.quoted.*
Expand All @@ -13,7 +10,6 @@ trait ErasureSameAsTypeMacros {

private[tapir] object ErasureSameAsTypeMacros {
def instanceImpl[T: Type](using Quotes): Expr[ErasureSameAsType[T]] = {
import quotes.reflect._
mustBeEqualToItsErasure[T]
'{ new ErasureSameAsType[T] {} }
}
Expand All @@ -31,7 +27,7 @@ private[tapir] object ErasureSameAsTypeMacros {
}

if (!isAllowed(t)) {
report.throwError(
report.errorAndAbort(
s"Type ${t.show}, $t is not the same as its erasure. Using a runtime-class-based check it won't be possible to verify " +
s"that the input matches the desired type. Use other methods to match the input to the appropriate variant " +
s"instead."
Expand Down
4 changes: 2 additions & 2 deletions core/src/main/scala-3/sttp/tapir/macros/FormCodecMacros.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ private[tapir] object FormCodecMacros {
val encodedNameAnnotationSymbol = TypeTree.of[Schema.annotations.encodedName].tpe.typeSymbol

def summonCodec[f: Type](field: CaseClassField[q.type, T]) = Expr.summon[Codec[List[String], f, CodecFormat.TextPlain]].getOrElse {
report.throwError(s"Cannot find Codec[List[String], T, CodecFormat.TextPlain]] for field: ${field}, of type: ${field.tpe}")
report.errorAndAbort(s"Cannot find Codec[List[String], T, CodecFormat.TextPlain]] for field: ${field}, of type: ${field.tpe}")
}

def encodeDefBody(tTerm: Term): Term = {
Expand Down Expand Up @@ -95,7 +95,7 @@ private[tapir] object FormCodecMacros {
'{
Codec.formSeqUtf8
.mapDecode($decodeExpr)($encodeExpr)
.schema(${ Expr.summon[Schema[T]].getOrElse(report.throwError(s"Cannot find a given Schema[${Type.show[T]}].")) })
.schema(${ Expr.summon[Schema[T]].getOrElse(report.errorAndAbort(s"Cannot find a given Schema[${Type.show[T]}].")) })
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package sttp.tapir.macros

import scala.annotation.compileTimeOnly
import scala.annotation.nowarn
import scala.collection.Factory

trait ModifyMacroSupport extends ModifyMacroFunctorSupport {
Expand All @@ -10,7 +10,7 @@ trait ModifyMacroSupport extends ModifyMacroFunctorSupport {
): ModifyFunctor[F, A] =
new ModifyFunctor[F, A] {}

implicit class ModifyEachMap[F[_, _], K, T](t: F[K, T])(implicit fac: Factory[(K, T), F[K, T]]) {
implicit class ModifyEachMap[F[_, _], K, T](t: F[K, T])(implicit @nowarn fac: Factory[(K, T), F[K, T]]) {
// @compileTimeOnly(canOnlyBeUsedInsideModify("each")) TODO
def each: T = sys.error("")
}
Expand Down
Loading

0 comments on commit 14bd314

Please sign in to comment.