diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..9cdec03 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,18 @@ +on: + push: + branches: + - master + pull_request: + branches: + - master +jobs: + core: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: coursier/cache-action@v6 + - uses: actions/setup-java@v2 + with: + distribution: 'temurin' + java-version: '8' + - run: ./mill __.__.test \ No newline at end of file diff --git a/.scalafmt.conf b/.scalafmt.conf index 4b4b44f..236c864 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -1,4 +1,4 @@ -version = 3.4.3 +version = 3.7.12 runner.dialect = scala3 maxColumn = 120 assumeStandardLibraryStripMargin = true @@ -7,23 +7,35 @@ indent { callSite = 4 } indentOperator.exemptScope = aloneEnclosed -align { - openParenCallSite = false - openParenDefnSite = false - tokens = [] +align.tokens = [] +rewrite { + rules = [Imports, SortModifiers] + trailingCommas.style = keep + imports { + expand = true + sort = original + } } newlines { source = keep + avoidForSimpleOverflow = [tooLong, slc] + topLevelStatementBlankLines = [ + { + maxNest = 0 + blanks = 1 + }, + { + minBreaks = 2 + blanks = 1 + } + ] } docstrings { + wrap = no style = Asterisk removeEmpty = true } -optIn { - annotationNewlines = true - breakChainOnFirstMethodDot = true - selfAnnotationNewline = true -} +binPack.parentConstructors = keep project.git = false fileOverride { diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index fceacec..0000000 --- a/.travis.yml +++ /dev/null @@ -1,9 +0,0 @@ -os: linux -dist: bionic -language: scala - -jdk: - - openjdk8 - -script: - - ./mill -i __.__.test \ No newline at end of file diff --git a/README.md b/README.md index f6d9fe2..afaadb1 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ ## Poppet [![Maven Central](https://img.shields.io/maven-central/v/com.github.yakivy/poppet-core_2.13.svg)](https://mvnrepository.com/search?q=poppet) [![Sonatype Nexus (Snapshots)](https://img.shields.io/nexus/s/https/oss.sonatype.org/com.github.yakivy/poppet-core_2.13.svg)](https://oss.sonatype.org/content/repositories/snapshots/com/github/yakivy/poppet-core_2.13/) -[![Build Status](https://app.travis-ci.com/yakivy/poppet.svg?branch=master)](https://app.travis-ci.com/github/yakivy/poppet) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) Cats friendly @@ -9,10 +8,10 @@ Poppet is a minimal, type-safe RPC Scala library. Essential differences from [autowire](https://github.com/lihaoyi/autowire): - has no explicit macro application `.call`, result of a consumer is an instance of original trait -- has no restricted HKT `Future`, you can specify any monad (has `cats.Monad` typeclass) as a processor HKT, and an arbitrary HKT for trait methods -- has no forced codec dependencies `uPickle`, you can choose from the list of predefined codecs or easily implement your own +- has no restricted HKT `Future`, you can specify any monad (has `cats.Monad` typeclass) as HKT for the provider/consumer +- has no forced codec dependencies `uPickle`, you can choose from the list of predefined codecs or easily implement your own codec - has robust failure handling mechanism -- supports Scala 3 (method/class generation with macros is still an experimental feature) +- supports Scala 3 (however method/class generation with macros is still an experimental feature) ### Table of contents 1. [Quick start](#quick-start) @@ -29,9 +28,9 @@ Essential differences from [autowire](https://github.com/lihaoyi/autowire): Put cats and poppet dependencies in the build file, let's assume you are using SBT: ```scala val version = new { - val cats = "2.9.0" - val circe = "0.14.3" - val poppet = "0.3.1" + val cats = "2.10.0" + val circe = "0.14.6" + val poppet = "0.3.4" } libraryDependencies ++= Seq( @@ -158,7 +157,7 @@ curl --location --request POST '${providerUrl}' \ ``` ### Limitations -You can generate consumer/provider almost from any Scala trait (or Java interface 😲). It can have non-abstract members, methods with default arguments, methods with multiple argument lists, etc... But there are several limitations: +You can generate consumer/provider almost from any Scala trait (or Java interface 😲). It can have non-abstract members, methods with default arguments, methods with multiple argument lists, varargs, etc... But there are several limitations: - you cannot overload methods with the same argument names, because for the sake of simplicity argument names are being used as a part of the request, for more info check [manual calls](#manual-calls) section: ```scala //compiles @@ -230,12 +229,15 @@ Provider[..., ...]() - add action (including argument name) to codec - throw an exception on duplicated service processor - separate `.service[S]` and `.service[G[_], S]` to simplify codec resolution -- add possibility to update service name, try to unify service name between Scala 2.x and 3.x - don't create ObjectMapper in the lib, use implicit one - check that passed class is a trait and doesn't have arguments to prevent obscure error from compiler +- check that all abstract methods are public ### Changelog +#### 0.3.4: +- fix compilation errors for methods with varargs + #### 0.3.3: - fix several compilation errors for Scala 3 diff --git a/build.sc b/build.sc index 3a664dc..0c0389a 100644 --- a/build.sc +++ b/build.sc @@ -10,20 +10,20 @@ import mill.playlib._ import com.github.lolgab.mill.crossplatform._ object versions { - val publish = "0.3.3" + val publish = "0.3.4" - val scala212 = "2.12.17" - val scala213 = "2.13.10" - val scala3 = "3.2.1" - val scalaJs = "1.11.0" - val scalaNative = "0.4.8" + val scala212 = "2.12.18" + val scala213 = "2.13.12" + val scala3 = "3.3.0" + val scalaJs = "1.13.2" + val scalaNative = "0.4.16" val scalatest = "3.2.14" - val cats = "2.9.0" + val cats = "2.10.0" val upickle = "2.0.0" - val circe = "0.14.3" - val playJson = "2.9.3" - val jackson = "2.13.4" + val circe = "0.14.6" + val playJson = "2.9.4" + val jackson = "2.13.5" val catsEffect = "3.4.1" val http4s = "0.23.12" diff --git a/core/src-2/poppet/consumer/core/ConsumerProcessorObjectBinCompat.scala b/core/src-2/poppet/consumer/core/ConsumerProcessorObjectBinCompat.scala index 0492b38..79fd7a4 100644 --- a/core/src-2/poppet/consumer/core/ConsumerProcessorObjectBinCompat.scala +++ b/core/src-2/poppet/consumer/core/ConsumerProcessorObjectBinCompat.scala @@ -5,12 +5,12 @@ import scala.language.experimental.macros import scala.reflect.macros.blackbox trait ConsumerProcessorObjectBinCompat { - implicit def apply[F[_], I, S]: ConsumerProcessor[F, I, S] = - macro ConsumerProcessorObjectBinCompat.applyImpl[F, I, S] + implicit def generate[F[_], I, S]: ConsumerProcessor[F, I, S] = + macro ConsumerProcessorObjectBinCompat.generateImpl[F, I, S] } object ConsumerProcessorObjectBinCompat { - def applyImpl[F[_], I, S]( + def generateImpl[F[_], I, S]( c: blackbox.Context)( implicit FT: c.WeakTypeTag[F[_]], IT: c.WeakTypeTag[I], ST: c.WeakTypeTag[S] ): c.Expr[ConsumerProcessor[F, I, S]] = { @@ -23,7 +23,7 @@ object ConsumerProcessorObjectBinCompat { val arguments = mInS.paramLists.map(ps => ps.map(p => q"${Ident(p.name)}: ${p.typeSignature}")) val (returnKind, returnType) = ProcessorMacro.separateReturnType(c)(FT.tpe, mInS.finalResultType, false) val codedArgument: c.universe.Symbol => Tree = a => q"""_root_.scala.Predef.implicitly[ - _root_.poppet.core.Codec[${a.typeSignature},${IT.tpe}] + _root_.poppet.core.Codec[${ProcessorMacro.unwrapVararg(c)(a.typeSignature)},${IT.tpe}] ].apply(${Ident(a.name)}).fold($$fh.apply, $fmonad.pure)""" val withCodedArguments: Tree => Tree = tree => mInS.paramLists.flatten match { case Nil => tree diff --git a/core/src-2/poppet/core/ProcessorMacro.scala b/core/src-2/poppet/core/ProcessorMacro.scala index d51d85b..888292a 100644 --- a/core/src-2/poppet/core/ProcessorMacro.scala +++ b/core/src-2/poppet/core/ProcessorMacro.scala @@ -32,6 +32,12 @@ object ProcessorMacro { } } + def unwrapVararg(c: blackbox.Context)(t: c.Type): c.Type = { + import c.universe._ + if (t.typeSymbol == definitions.RepeatedParamClass) appliedType(typeOf[Seq[_]], t.typeArgs) + else t + } + def separateReturnType( c: blackbox.Context)(fType: c.Type, returnType: c.Type, fromReturn: Boolean ): (c.Type, c.Type) = { diff --git a/core/src-2/poppet/provider/core/ProviderProcessorObjectBinCompat.scala b/core/src-2/poppet/provider/core/ProviderProcessorObjectBinCompat.scala index fc82471..0cca677 100644 --- a/core/src-2/poppet/provider/core/ProviderProcessorObjectBinCompat.scala +++ b/core/src-2/poppet/provider/core/ProviderProcessorObjectBinCompat.scala @@ -5,12 +5,12 @@ import scala.language.experimental.macros import scala.reflect.macros.blackbox trait ProviderProcessorObjectBinCompat { - implicit def apply[F[_], I, S]: ProviderProcessor[F, I, S] = - macro ProviderProcessorObjectBinCompat.applyImpl[F, I, S] + implicit def generate[F[_], I, S]: ProviderProcessor[F, I, S] = + macro ProviderProcessorObjectBinCompat.generateImpl[F, I, S] } object ProviderProcessorObjectBinCompat { - def applyImpl[F[_], I, S]( + def generateImpl[F[_], I, S]( c: blackbox.Context)( implicit FT: c.WeakTypeTag[F[_]], IT: c.WeakTypeTag[I], ST: c.WeakTypeTag[S] ): c.Expr[ProviderProcessor[F, I, S]] = { @@ -21,7 +21,7 @@ object ProviderProcessorObjectBinCompat { val argumentNames = m.paramLists.flatten.map(_.name.toString) val (returnKind, returnType) = ProcessorMacro.separateReturnType(c)(FT.tpe, mInS.finalResultType, true) val codedArgument: c.universe.Symbol => Tree = a => q"""_root_.scala.Predef.implicitly[ - _root_.poppet.core.Codec[$IT,${a.typeSignature}] + _root_.poppet.core.Codec[$IT,${ProcessorMacro.unwrapVararg(c)(a.typeSignature)}] ].apply(as(${a.name.toString})).fold($$fh.apply, $fmonad.pure)""" val withCodedArguments: Tree => Tree = tree => mInS.paramLists.flatten match { case Nil => tree @@ -38,17 +38,26 @@ object ProviderProcessorObjectBinCompat { returnKind, returnType, mInS.finalResultType, FT.tpe, IT.tpe, appliedType(FT.tpe, IT.tpe), ) - val groupedArguments = m.paramLists.map(pl => pl.map(p => Ident(p.name))) + val groupedArguments = m.paramLists.map(pl => pl.map(p => p.typeSignature.typeSymbol -> Ident(p.name))) q"""new _root_.poppet.provider.core.MethodProcessor[$FT, $IT]( ${ST.tpe.typeSymbol.fullName}, ${m.name.toString}, _root_.scala.List(..$argumentNames), as => ${withCodedArguments(q""" - $fmonad.flatMap($returnKindCodec.apply(${groupedArguments.foldLeft[Tree]( - q"$$service.${m.name.toTermName}")((acc, pl) => Apply(acc, pl) - )}))(_root_.scala.Predef.implicitly[ - _root_.poppet.core.Codec[$returnType,${IT.tpe}] - ].apply(_).fold($$fh.apply, $fmonad.pure)) + $fmonad.flatMap( + $returnKindCodec.apply(${ + groupedArguments.foldLeft[Tree](q"$$service.${m.name.toTermName}") { (acc, pl) => + pl.lastOption match { + case Some((s, i)) if s == definitions.RepeatedParamClass => + q"$acc(..${pl.init.map(_._2)}, $i: _*)" + case _ => q"$acc(..${pl.map(_._2)})" + } + } + }) + )( + _root_.scala.Predef.implicitly[_root_.poppet.core.Codec[$returnType,${IT.tpe}]] + .apply(_).fold($$fh.apply, $fmonad.pure) + ) """)} )""" } diff --git a/core/src-3/poppet/consumer/core/ConsumerProcessorObjectBinCompat.scala b/core/src-3/poppet/consumer/core/ConsumerProcessorObjectBinCompat.scala index 572ae74..18d3f51 100644 --- a/core/src-3/poppet/consumer/core/ConsumerProcessorObjectBinCompat.scala +++ b/core/src-3/poppet/consumer/core/ConsumerProcessorObjectBinCompat.scala @@ -63,9 +63,9 @@ object ConsumerProcessorObjectBinCompat { ): q.reflect.Term = { import q.reflect._ val methodReturnTpe = methodSymbol.tree.asInstanceOf[DefDef].returnTpt.tpe - def codedArgument(a: Tree): Expr[F[I]] = resolveTypeMember( + def codedArgument(a: Tree): Expr[F[I]] = unwrapVararg(resolveTypeMember( TypeRepr.of[S], Ref(a.symbol).tpe.widen - ).asType match { case '[at] => + )).asType match { case '[at] => '{ summonInline[Codec[at,I]].apply(${Ref.term(a.symbol.termRef).asExprOf[at]}).fold($fh.apply, $MF.pure) } } def codedArguments(terms: List[Tree]): Expr[F[Map[String, I]]] = { diff --git a/core/src-3/poppet/core/ProcessorMacro.scala b/core/src-3/poppet/core/ProcessorMacro.scala index a16d28d..e4eec20 100644 --- a/core/src-3/poppet/core/ProcessorMacro.scala +++ b/core/src-3/poppet/core/ProcessorMacro.scala @@ -25,6 +25,17 @@ object ProcessorMacro { methods } + def unwrapVararg(using q: Quotes)(tpe: q.reflect.TypeRepr) = { + import q.reflect._ + tpe match { + case AnnotatedType(tpeP, t) if t.tpe.typeSymbol == defn.RepeatedAnnot => + TypeRepr.of[Seq].appliedTo(tpeP.typeArgs) + case tpe if tpe.typeSymbol == defn.RepeatedParamClass => + TypeRepr.of[Seq].appliedTo(tpe.typeArgs) + case _ => tpe + } + } + def resolveTypeMember( using q: Quotes)( owner: q.reflect.TypeRepr, diff --git a/core/src-3/poppet/provider/core/ProviderProcessorObjectBinCompat.scala b/core/src-3/poppet/provider/core/ProviderProcessorObjectBinCompat.scala index 5d32fe1..49bc901 100644 --- a/core/src-3/poppet/provider/core/ProviderProcessorObjectBinCompat.scala +++ b/core/src-3/poppet/provider/core/ProviderProcessorObjectBinCompat.scala @@ -29,7 +29,7 @@ object ProviderProcessorObjectBinCompat { val serviceName = TypeRepr.of[S].show val methodProcessors = getAbstractMethods[S].map { m => def decodeArg(arg: ValDef): Expr[Map[String, I] => F[Any]] = { - resolveTypeMember(TypeRepr.of[S], arg.tpt.tpe).asType match { case '[at] => '{ input => + unwrapVararg(resolveTypeMember(TypeRepr.of[S], arg.tpt.tpe)).asType match { case '[at] => '{ input => summonInline[Codec[I, at]] .apply(input(${Literal(StringConstant(arg.name)).asExprOf[String]})) .fold($fh.apply, $MF.pure) @@ -48,7 +48,9 @@ object ProviderProcessorObjectBinCompat { TypeRepr.of[F], TypeRepr.of[I], TypeRepr.of[F[I]], ) val callService: Expr[Map[String, I] => F[I]] = returnType.asType match { case '[rt] => - val paramTypes = m.termParamss.map(_.params.map(t => resolveTypeMember(TypeRepr.of[S], t.tpt.tpe))) + val paramTypes = m.termParamss.map(_.params.map(arg => + arg -> resolveTypeMember(TypeRepr.of[S], arg.tpt.tpe) + )) '{ input => $MF.flatMap( $decodeArgs(input))(ast => $MF.flatMap( @@ -56,10 +58,19 @@ object ProviderProcessorObjectBinCompat { Select.unique(returnKindCodec, "apply"), List(TypeTree.of[rt])), List(paramTypes.foldLeft[(Term, Int)]( - Select(service.asTerm, m.symbol) -> 0)((acc, item) => - Apply(acc._1, item.zipWithIndex.map{ t => t._1.asType match { case '[at] => - '{ast.apply(${Literal(IntConstant(t._2 + acc._2)).asExprOf[Int]}).asInstanceOf[at]}.asTerm - }}) -> (item.size + acc._2) + Select(service.asTerm, m.symbol) -> 0)((acc, item) => ( + Apply(acc._1, item.zipWithIndex.map{ case ((arg, t), i) => + t.asType match { case '[at] => + val term = '{ast.apply(${Literal(IntConstant(i + acc._2)).asExprOf[Int]}).asInstanceOf[at]}.asTerm + arg.tpt.tpe match { + case AnnotatedType(tpeP, t) if t.tpe.typeSymbol == defn.RepeatedAnnot => + Typed(term, Inferred(defn.RepeatedParamClass.typeRef.appliedTo(tpeP.typeArgs))) + case _ => term + } + } + }), + (item.size + acc._2) + ) )._1) ).asExprOf[F[rt]]})( ${returnTypeCodec.asExprOf[Codec[rt, I]]}.apply(_).fold($fh.apply, $MF.pure) diff --git a/core/src/poppet/consumer/core/ConsumerProcessor.scala b/core/src/poppet/consumer/core/ConsumerProcessor.scala index 3c3af60..851d075 100644 --- a/core/src/poppet/consumer/core/ConsumerProcessor.scala +++ b/core/src/poppet/consumer/core/ConsumerProcessor.scala @@ -8,4 +8,6 @@ trait ConsumerProcessor[F[_], I, S] { def apply(client: Request[I] => F[Response[I]], fh: FailureHandler[F]): S } -object ConsumerProcessor extends ConsumerProcessorObjectBinCompat +object ConsumerProcessor extends ConsumerProcessorObjectBinCompat { + def apply[F[_], I, S](implicit instance: ConsumerProcessor[F, I, S]): ConsumerProcessor[F, I, S] = instance +} diff --git a/core/src/poppet/provider/core/Provider.scala b/core/src/poppet/provider/core/Provider.scala index 029cd12..382e547 100644 --- a/core/src/poppet/provider/core/Provider.scala +++ b/core/src/poppet/provider/core/Provider.scala @@ -25,7 +25,7 @@ class Provider[F[_] : Monad, I]( private val indexedProcessors: Map[String, Map[String, Map[String, Map[String, I] => F[I]]]] = processors.groupBy(_.service).mapValues( _.groupBy(_.name).mapValues( - _.map(m => m.arguments.sorted.mkString(",") -> m.f).toMap + _.map(m => m.arguments.toList.sorted.mkString(",") -> m.f).toMap ).toMap ).toMap diff --git a/core/src/poppet/provider/core/ProviderProcessor.scala b/core/src/poppet/provider/core/ProviderProcessor.scala index 77b851b..198b78a 100644 --- a/core/src/poppet/provider/core/ProviderProcessor.scala +++ b/core/src/poppet/provider/core/ProviderProcessor.scala @@ -10,4 +10,6 @@ class MethodProcessor[F[_], I]( val service: String, val name: String, val arguments: List[String], val f: Map[String, I] => F[I] ) -object ProviderProcessor extends ProviderProcessorObjectBinCompat +object ProviderProcessor extends ProviderProcessorObjectBinCompat { + def apply[F[_], I, S](implicit instance: ProviderProcessor[F, I, S]): ProviderProcessor[F, I, S] = instance +} diff --git a/core/test/src/poppet/consumer/ConsumerProcessorSpec.scala b/core/test/src/poppet/consumer/ConsumerProcessorSpec.scala index bc9bd49..5749123 100644 --- a/core/test/src/poppet/consumer/ConsumerProcessorSpec.scala +++ b/core/test/src/poppet/consumer/ConsumerProcessorSpec.scala @@ -19,6 +19,7 @@ class ConsumerProcessorSpec extends AsyncFreeSpec with ProcessorSpec { implicit val c3: Codec[String, List[String]] = a => Right(List(a)) implicit val cp0: Codec[Boolean, String] = a => Right(a.toString) implicit val cp1: Codec[Option[Boolean], String] = a => Right(a.getOrElse(false).toString) + implicit val cp2: Codec[Seq[Boolean], String] = a => Right(a.mkString(",")) var request: Request[String] = null "when has id data kind" - { @@ -30,16 +31,24 @@ class ConsumerProcessorSpec extends AsyncFreeSpec with ProcessorSpec { val a = ConsumerProcessor[Id, String, Simple].apply(client, FailureHandler.throwing) assert(a.a0 == 0 && request == Request[String]( - "poppet.core.ProcessorSpec.Simple", "a0", Map.empty + "poppet.core.ProcessorSpec.Simple", + "a0", + Map.empty )) assert(a.a00() == List(0) && request == Request[String]( - "poppet.core.ProcessorSpec.Simple", "a00", Map.empty + "poppet.core.ProcessorSpec.Simple", + "a00", + Map.empty )) assert(a.a1(true) == SimpleDto(0) && request == Request( - "poppet.core.ProcessorSpec.Simple", "a1", Map("b" -> "true") + "poppet.core.ProcessorSpec.Simple", + "a1", + Map("b" -> "true") )) assert(a.a2(true, None) == List("0") && request == Request( - "poppet.core.ProcessorSpec.Simple", "a2", Map("b0" -> "true", "b1" -> "false") + "poppet.core.ProcessorSpec.Simple", + "a2", + Map("b0" -> "true", "b1" -> "false") )) } "for methods with future kind" in { @@ -49,59 +58,98 @@ class ConsumerProcessorSpec extends AsyncFreeSpec with ProcessorSpec { val a = ConsumerProcessor[Id, String, WithFutureKind].apply(client, FailureHandler.throwing) assert(a.a0.value.get.get == 0 && request == Request[String]( - "poppet.core.ProcessorSpec.WithFutureKind", "a0", Map.empty + "poppet.core.ProcessorSpec.WithFutureKind", + "a0", + Map.empty )) assert(a.a1.value.get.get == List(0) && request == Request[String]( - "poppet.core.ProcessorSpec.WithFutureKind", "a1", Map.empty + "poppet.core.ProcessorSpec.WithFutureKind", + "a1", + Map.empty )) } "for methods with multiple argument lists" in { - val a = ConsumerProcessor[Id, String, WithMultipleArgumentLists].apply(client, FailureHandler.throwing) + val a = + ConsumerProcessor[Id, String, WithMultipleArgumentLists].apply(client, FailureHandler.throwing) assert(a.a0(true)(false) == 0 && request == Request( - "poppet.core.ProcessorSpec.WithMultipleArgumentLists", "a0", Map( - "b0" -> "true", "b1" -> "false" + "poppet.core.ProcessorSpec.WithMultipleArgumentLists", + "a0", + Map( + "b0" -> "true", + "b1" -> "false" ) )) assert(a.a1(true)()(false) == List(0) && request == Request( - "poppet.core.ProcessorSpec.WithMultipleArgumentLists", "a1", Map( - "b0" -> "true", "b1" -> "false" + "poppet.core.ProcessorSpec.WithMultipleArgumentLists", + "a1", + Map( + "b0" -> "true", + "b1" -> "false" ) )) assert(a.a2(true)(false, true) == SimpleDto(0) && request == Request( - "poppet.core.ProcessorSpec.WithMultipleArgumentLists", "a2", Map( - "b0" -> "true", "b10" -> "false", "b11" -> "true" + "poppet.core.ProcessorSpec.WithMultipleArgumentLists", + "a2", + Map( + "b0" -> "true", + "b10" -> "false", + "b11" -> "true" ) )) } "for methods with default arguments" in { - val a: WithDefaultArguments = ConsumerProcessor[Id, String, WithDefaultArguments].apply(client, FailureHandler.throwing) + val a: WithDefaultArguments = + ConsumerProcessor[Id, String, WithDefaultArguments].apply(client, FailureHandler.throwing) assert(a.a0(false) == 0 && request == Request( - "poppet.core.ProcessorSpec.WithDefaultArguments", "a0", Map("b" -> "false") + "poppet.core.ProcessorSpec.WithDefaultArguments", + "a0", + Map("b" -> "false") )) assert(a.a0() == 0 && request == Request( - "poppet.core.ProcessorSpec.WithDefaultArguments", "a0", Map("b" -> "true") + "poppet.core.ProcessorSpec.WithDefaultArguments", + "a0", + Map("b" -> "true") )) assert(a.a1(false) == List(0) && request == Request( - "poppet.core.ProcessorSpec.WithDefaultArguments", "a1", Map("b0" -> "false", "b1" -> "true") + "poppet.core.ProcessorSpec.WithDefaultArguments", + "a1", + Map("b0" -> "false", "b1" -> "true") )) assert(a.a1(true, false) == List(0) && request == Request( - "poppet.core.ProcessorSpec.WithDefaultArguments", "a1", Map("b0" -> "true", "b1" -> "false") + "poppet.core.ProcessorSpec.WithDefaultArguments", + "a1", + Map("b0" -> "true", "b1" -> "false") )) assert(a.a2(false, false) == SimpleDto(0) && request == Request( - "poppet.core.ProcessorSpec.WithDefaultArguments", "a2", Map( - "b0" -> "false", "b1" -> "false", "b2" -> "true", "b3" -> "true" + "poppet.core.ProcessorSpec.WithDefaultArguments", + "a2", + Map( + "b0" -> "false", + "b1" -> "false", + "b2" -> "true", + "b3" -> "true" ) )) assert(a.a2(true, true, false) == SimpleDto(0) && request == Request( - "poppet.core.ProcessorSpec.WithDefaultArguments", "a2", Map( - "b0" -> "true", "b1" -> "true", "b2" -> "false", "b3" -> "true" + "poppet.core.ProcessorSpec.WithDefaultArguments", + "a2", + Map( + "b0" -> "true", + "b1" -> "true", + "b2" -> "false", + "b3" -> "true" ) )) assert(a.a2(true, true, false, false) == SimpleDto(0) && request == Request( - "poppet.core.ProcessorSpec.WithDefaultArguments", "a2", Map( - "b0" -> "true", "b1" -> "true", "b2" -> "false", "b3" -> "false" + "poppet.core.ProcessorSpec.WithDefaultArguments", + "a2", + Map( + "b0" -> "true", + "b1" -> "true", + "b2" -> "false", + "b3" -> "false" ) )) } @@ -110,10 +158,24 @@ class ConsumerProcessorSpec extends AsyncFreeSpec with ProcessorSpec { .apply(client, FailureHandler.throwing) assert(a.a0(false) == 0 && request == Request( - "poppet.core.ProcessorSpec.WithParentWithParameters", "a0", Map("b0" -> "false") + "poppet.core.ProcessorSpec.WithParentWithParameters", + "a0", + Map("b0" -> "false") )) assert(a.a1 == 0 && request == Request[String]( - "poppet.core.ProcessorSpec.WithParentWithParameters", "a1", Map.empty + "poppet.core.ProcessorSpec.WithParentWithParameters", + "a1", + Map.empty + )) + } + "for methods with varargs" in { + val a = ConsumerProcessor[Id, String, WithVarargs] + .apply(client, FailureHandler.throwing) + + assert(a.a0(false, true) == 0 && request == Request( + "poppet.core.ProcessorSpec.WithVarargs", + "a0", + Map("b" -> "false,true") )) } } @@ -134,16 +196,24 @@ class ConsumerProcessorSpec extends AsyncFreeSpec with ProcessorSpec { ) assert(a.a0 == 0 && request == Request[String]( - "poppet.core.ProcessorSpec.Simple", "a0", Map.empty + "poppet.core.ProcessorSpec.Simple", + "a0", + Map.empty )) assert(a.a00() == List(0) && request == Request[String]( - "poppet.core.ProcessorSpec.Simple", "a00", Map.empty + "poppet.core.ProcessorSpec.Simple", + "a00", + Map.empty )) assert(a.a1(true) == SimpleDto(0) && request == Request( - "poppet.core.ProcessorSpec.Simple", "a1", Map("b" -> "true") + "poppet.core.ProcessorSpec.Simple", + "a1", + Map("b" -> "true") )) assert(a.a2(true, Option(false)) == List("0") && request == Request( - "poppet.core.ProcessorSpec.Simple", "a2", Map("b0" -> "true", "b1" -> "false") + "poppet.core.ProcessorSpec.Simple", + "a2", + Map("b0" -> "true", "b1" -> "false") )) } "when has complex data kind" in { @@ -167,10 +237,14 @@ class ConsumerProcessorSpec extends AsyncFreeSpec with ProcessorSpec { value.value.value.get.get.toOption.get assert(result(p.a0(true)) == 0 && request == Request( - "poppet.core.ProcessorSpec.WithComplexReturnTypes", "a0", Map("b" -> "true") + "poppet.core.ProcessorSpec.WithComplexReturnTypes", + "a0", + Map("b" -> "true") )) assert(result(p.a1(true, false)) == 0 && request == Request( - "poppet.core.ProcessorSpec.WithComplexReturnTypes", "a1", Map("b0" -> "true", "b1" -> "false") + "poppet.core.ProcessorSpec.WithComplexReturnTypes", + "a1", + Map("b0" -> "true", "b1" -> "false") )) } "when has A data kind and service has B data kind" in { @@ -198,10 +272,14 @@ class ConsumerProcessorSpec extends AsyncFreeSpec with ProcessorSpec { value.value.value.get.get.toOption.get assert(result(p.a0(true)) == 0 && request == Request( - "poppet.core.ProcessorSpec.WithComplexReturnTypes", "a0", Map("b" -> "true") + "poppet.core.ProcessorSpec.WithComplexReturnTypes", + "a0", + Map("b" -> "true") )) assert(result(p.a1(true, true)) == 0 && request == Request( - "poppet.core.ProcessorSpec.WithComplexReturnTypes", "a1", Map("b0" -> "true", "b1" -> "true") + "poppet.core.ProcessorSpec.WithComplexReturnTypes", + "a1", + Map("b0" -> "true", "b1" -> "true") )) } } @@ -225,7 +303,7 @@ class ConsumerProcessorSpec extends AsyncFreeSpec with ProcessorSpec { "for trait with conflicting methods" in { assertCompilationErrorMessage( assertCompiles("""ConsumerProcessor[Id, String, WithConflictedMethods]""".stripMargin), - "Use unique argument name lists for overloaded methods.", + "Use unique argument name lists for overloaded methods." ) } "for trait with abstract type" in { @@ -235,14 +313,6 @@ class ConsumerProcessorSpec extends AsyncFreeSpec with ProcessorSpec { "poppet.core.ProcessorSpec.WithAbstractType.A" ) } - "for valid trait without simple codec" in { - assertCompilationErrorMessage( - assertCompiles("""ConsumerProcessor[Id, String, Simple]"""), - "Unable to convert cats.Id[String] to Int. Try to provide poppet.Codec[String,Int].", - "Unable to convert cats.Id[scala.Predef.String] to scala.Int. " + - "Try to provide poppet.Codec[scala.Predef.String,scala.Int].", - ) - } "for valid trait with ambiguous simple codec" in { implicit val c0: Codec[String, Int] = a => Right(a.toInt) implicit val c1: Codec[String, Int] = c0 @@ -255,95 +325,105 @@ class ConsumerProcessorSpec extends AsyncFreeSpec with ProcessorSpec { "both value c1 and value c0 match type poppet.core.Codec[String, Int]" ) } + "for valid trait without simple codec" in { + assertCompilationErrorMessagePattern( + assertCompiles("""ConsumerProcessor[Id, String, Simple]"""), + ("Unable to convert " + + "((cats.)?Id\\[(scala.Predef.|java.lang.)?String]|(scala.Predef.|java.lang.)?String) to " + + "(scala.)?Int. Try to provide poppet.Codec\\[(scala.Predef.|java.lang.)?String,(scala.)?Int].").r + ) + } "for valid trait without codec for type with argument" in { implicit val c0: Codec[String, Int] = a => Right(a.toInt) - assertCompilationErrorMessage( + assertCompilationErrorMessagePattern( assertCompiles("""ConsumerProcessor[Id, String, Simple]"""), - "Unable to convert cats.Id[String] to List[Int]. " + - "Try to provide poppet.Codec[String,List[Int]] or poppet.CodecK[cats.Id,List].", - "Unable to convert cats.Id[scala.Predef.String] to scala.collection.immutable.List[scala.Int]. " + - "Try to provide poppet.Codec[scala.Predef.String,scala.collection.immutable.List[scala.Int]] or " + - "poppet.CodecK[cats.Id,scala.collection.immutable.List].", + ("Unable to convert " + + "((cats.)?Id\\[(scala.Predef.|java.lang.)?String]|(scala.Predef.|java.lang.)?String) to " + + "(scala.collection.immutable.)?List\\[(scala.)?Int]. " + + "Try to provide " + + "poppet.Codec\\[(scala.Predef.|java.lang.)?String,(scala.collection.immutable.)?List\\[(scala.)?Int]] " + + "or " + + "poppet.CodecK\\[(\\[A\\])?(cats.)?Id(\\[A\\])?,(scala.collection.immutable.)?List].").r ) } "for valid trait without codec for simple type with explicit Id kind" in { implicit val c0: Codec[String, Int] = a => Right(a.toInt) implicit val c1: Codec[String, List[Int]] = a => Right(List(a.toInt)) implicit val c2: Codec[String, SimpleDto] = a => Right(SimpleDto(a.toInt)) - assertCompilationErrorMessage( + assertCompilationErrorMessagePattern( assertCompiles("""ConsumerProcessor[Id, String, Simple]"""), - "Unable to convert cats.Id[String] to cats.Id[List[String]]. " + - "Try to provide poppet.Codec[String,List[String]].", - "Unable to convert cats.Id[scala.Predef.String] to cats.Id[scala.collection.immutable.List[scala.Predef.String]]. " + - "Try to provide poppet.Codec[scala.Predef.String,scala.collection.immutable.List[scala.Predef.String]].", + ("Unable to convert " + + "((cats.)?Id\\[(scala.Predef.|java.lang.)?String]|(scala.Predef.|java.lang.)?String) to " + + "(cats.)?Id\\[(scala.collection.immutable.)?List\\[(scala.Predef.|java.lang.)?String]]. " + + "Try to provide " + + "poppet.Codec\\[(scala.Predef.|java.lang.)?String,(scala.collection.immutable.)?List\\[(scala.Predef.|java.lang.)?String]].").r ) } "for valid trait without codec for simple type with Future kind" in { - assertCompilationErrorMessage( + assertCompilationErrorMessagePattern( assertCompiles("""ConsumerProcessor[Id, String, WithFutureKind]"""), - "Unable to convert cats.Id[String] to scala.concurrent.Future[Int]. " + - "Try to provide poppet.Codec[String,scala.concurrent.Future[Int]] " + - "or poppet.CodecK[cats.Id,scala.concurrent.Future] with poppet.Codec[String,Int].", - "Unable to convert cats.Id[scala.Predef.String] to scala.concurrent.Future[scala.Int]. " + - "Try to provide poppet.Codec[scala.Predef.String,scala.concurrent.Future[scala.Int]] " + - "or poppet.CodecK[cats.Id,scala.concurrent.Future] with poppet.Codec[scala.Predef.String,scala.Int].", + ("Unable to convert " + + "((cats.)?Id\\[(scala.Predef.|java.lang.)?String]|(scala.Predef.|java.lang.)?String) to " + + "scala.concurrent.Future\\[(scala.)?Int]. " + + "Try to provide " + + "poppet.Codec\\[(scala.Predef.|java.lang.)?String,scala.concurrent.Future\\[(scala.)?Int]] " + + "or " + + "poppet.CodecK\\[(\\[A\\])?(cats.)?Id(\\[A\\])?,(\\[\\+T\\])?scala.concurrent.Future(\\[T\\])?] " + + "with poppet.Codec\\[(scala.Predef.|java.lang.)?String,(scala.)?Int].").r ) } "for valid trait without codec for simple type with Future kind, but with codecK" in { implicit val ck = new CodecK[Id, Future] { override def apply[A](a: Id[A]): Future[A] = Future.successful(a) } - assertCompilationErrorMessage( + assertCompilationErrorMessagePattern( assertCompiles("""ConsumerProcessor[Id, String, WithFutureKind]"""), - "Unable to convert cats.Id[String] to scala.concurrent.Future[Int]. " + - "Try to provide poppet.Codec[String,Int].", - "Unable to convert cats.Id[scala.Predef.String] to scala.concurrent.Future[scala.Int]. " + - "Try to provide poppet.Codec[scala.Predef.String,scala.Int].", + ("Unable to convert " + + "((cats.)?Id\\[(scala.Predef.|java.lang.)?String]|(scala.Predef.|java.lang.)?String) to " + + "scala.concurrent.Future\\[(scala.)?Int]. " + + "Try to provide poppet.Codec\\[(scala.Predef.|java.lang.)?String,(scala.)?Int].").r ) } "for valid trait without codecK for simple type with Future kind, but with codec" in { implicit val c0: Codec[String, Int] = a => Right(a.toInt) - assertCompilationErrorMessage( + assertCompilationErrorMessagePattern( assertCompiles("""ConsumerProcessor[Id, String, WithFutureKind]"""), - "Unable to convert cats.Id[String] to scala.concurrent.Future[Int]. " + - "Try to provide poppet.Codec[String,scala.concurrent.Future[Int]] " + - "or poppet.CodecK[cats.Id,scala.concurrent.Future].", - "Unable to convert cats.Id[scala.Predef.String] to scala.concurrent.Future[scala.Int]. " + - "Try to provide poppet.Codec[scala.Predef.String,scala.concurrent.Future[scala.Int]] " + - "or poppet.CodecK[cats.Id,scala.concurrent.Future].", + ("Unable to convert " + + "((cats.)?Id\\[(scala.Predef.|java.lang.)?String]|(scala.Predef.|java.lang.)?String) to " + + "scala.concurrent.Future\\[(scala.)?Int]. " + + "Try to " + + "provide poppet.Codec\\[(scala.Predef.|java.lang.)?String,scala.concurrent.Future\\[(scala.)?Int]] " + + "or " + + "poppet.CodecK\\[(\\[A\\])?(cats.)?Id(\\[A\\])?,(\\[\\+T\\])?scala.concurrent.Future(\\[T\\])?].").r ) } } "when has Future data kind" - { "for valid trait without simple codec" in { - assertCompilationErrorMessage( + assertCompilationErrorMessagePattern( assertCompiles("""ConsumerProcessor[Future, String, Simple]"""), - "Unable to convert scala.concurrent.Future[String] to Int. " + - "Try to provide poppet.CodecK[scala.concurrent.Future,cats.Id] with poppet.Codec[String,Int].", - "Unable to convert scala.concurrent.Future[scala.Predef.String] to scala.Int. " + - "Try to provide poppet.CodecK[scala.concurrent.Future,cats.Id] with poppet.Codec[scala.Predef.String,scala.Int].", + ("Unable to convert scala.concurrent.Future\\[(scala.Predef.|java.lang.)?String] to (scala.)?Int. " + + "Try to provide " + + "poppet.CodecK\\[(\\[\\+T\\])?scala.concurrent.Future(\\[T\\])?,(cats.)?Id] with " + + "poppet.Codec\\[(scala.Predef.|java.lang.)?String,(scala.)?Int].").r ) } "for valid trait without simple codec, but with codecK" in { implicit val ck = new CodecK[Future, Id] { override def apply[A](a: Future[A]): Id[A] = a.value.get.get } - assertCompilationErrorMessage( + assertCompilationErrorMessagePattern( assertCompiles("""ConsumerProcessor[Future, String, Simple]"""), - "Unable to convert scala.concurrent.Future[String] to Int. " + - "Try to provide poppet.Codec[String,Int].", - "Unable to convert scala.concurrent.Future[scala.Predef.String] to scala.Int. " + - "Try to provide poppet.Codec[scala.Predef.String,scala.Int].", + ("Unable to convert scala.concurrent.Future\\[(scala.Predef.|java.lang.)?String] to (scala.)?Int. " + + "Try to provide poppet.Codec\\[(scala.Predef.|java.lang.)?String,(scala.)?Int].").r ) } "for valid trait without simple codecK, but with codec" in { implicit val c0: Codec[String, Int] = a => Right(a.toInt) - assertCompilationErrorMessage( + assertCompilationErrorMessagePattern( assertCompiles("""ConsumerProcessor[Future, String, Simple]"""), - "Unable to convert scala.concurrent.Future[String] to Int. " + - "Try to provide poppet.CodecK[scala.concurrent.Future,cats.Id].", - "Unable to convert scala.concurrent.Future[scala.Predef.String] to scala.Int. " + - "Try to provide poppet.CodecK[scala.concurrent.Future,cats.Id].", + ("Unable to convert scala.concurrent.Future\\[(scala.Predef.|java.lang.)?String] to (scala.)?Int. " + + "Try to provide poppet.CodecK\\[(\\[\\+T\\])?scala.concurrent.Future(\\[T\\])?,(cats.)?Id].").r ) } "for valid trait without codec for type with argument" in { @@ -351,13 +431,15 @@ class ConsumerProcessorSpec extends AsyncFreeSpec with ProcessorSpec { override def apply[A](a: Future[A]): Id[A] = a.value.get.get } implicit val c0: Codec[String, Int] = a => Right(a.toInt) - assertCompilationErrorMessage( + assertCompilationErrorMessagePattern( assertCompiles("""ConsumerProcessor[Future, String, Simple]"""), - "Unable to convert scala.concurrent.Future[String] to List[Int]. " + - "Try to provide poppet.Codec[String,List[Int]] or poppet.CodecK[scala.concurrent.Future,List].", - "Unable to convert scala.concurrent.Future[scala.Predef.String] to scala.collection.immutable.List[scala.Int]. " + - "Try to provide poppet.Codec[scala.Predef.String,scala.collection.immutable.List[scala.Int]] or " + - "poppet.CodecK[scala.concurrent.Future,scala.collection.immutable.List].", + ("Unable to convert " + + "scala.concurrent.Future\\[(scala.Predef.|java.lang.)?String] to " + + "(scala.collection.immutable.)?List\\[(scala.)?Int]. " + + "Try to provide " + + "poppet.Codec\\[(scala.Predef.|java.lang.)?String,(scala.collection.immutable.)?List\\[(scala.)?Int]] " + + "or " + + "poppet.CodecK\\[(\\[\\+T\\])?scala.concurrent.Future(\\[T\\])?,(scala.collection.immutable.)?List].").r ) } "for valid trait without codec for simple type with explicit Id kind" in { @@ -367,12 +449,13 @@ class ConsumerProcessorSpec extends AsyncFreeSpec with ProcessorSpec { implicit val c0: Codec[String, Int] = a => Right(a.toInt) implicit val c1: Codec[String, List[Int]] = a => Right(List(a.toInt)) implicit val c2: Codec[String, SimpleDto] = a => Right(SimpleDto(a.toInt)) - assertCompilationErrorMessage( + assertCompilationErrorMessagePattern( assertCompiles("""ConsumerProcessor[Future, String, Simple]"""), - "Unable to convert scala.concurrent.Future[String] to cats.Id[List[String]]. " + - "Try to provide poppet.Codec[String,List[String]].", - "Unable to convert scala.concurrent.Future[scala.Predef.String] to cats.Id[scala.collection.immutable.List[scala.Predef.String]]. " + - "Try to provide poppet.Codec[scala.Predef.String,scala.collection.immutable.List[scala.Predef.String]].", + ("Unable to convert " + + "scala.concurrent.Future\\[(scala.Predef.|java.lang.)?String] to " + + "(cats.)?Id\\[(scala.collection.immutable.)?List\\[(scala.Predef.|java.lang.)?String]]. " + + "Try to provide " + + "poppet.Codec\\[(scala.Predef.|java.lang.)?String,(scala.collection.immutable.)?List\\[(scala.Predef.|java.lang.)?String]].").r ) } } diff --git a/core/test/src/poppet/core/ProcessorSpec.scala b/core/test/src/poppet/core/ProcessorSpec.scala index 2871177..541b3ea 100644 --- a/core/test/src/poppet/core/ProcessorSpec.scala +++ b/core/test/src/poppet/core/ProcessorSpec.scala @@ -7,6 +7,7 @@ import org.scalatest.exceptions.TestFailedException import org.scalactic.source.Position import poppet.PoppetSpec import scala.concurrent.Future +import scala.util.matching.Regex object ProcessorSpec { case class SimpleDto(value: Int) @@ -34,11 +35,14 @@ object ProcessorSpec { def a0(b0: A): Int def a1: B } + trait WithVarargs { + def a0(b: Boolean*): Int + } trait WithParentWithParameters extends WithParameters[Boolean, Int] trait WithComplexReturnTypes { def a0(b: Boolean): WithComplexReturnTypes.ReturnType[Int] def a1(b0: Boolean, b1: Boolean): WithComplexReturnTypes.ReturnType[Int] - //def b: Either[String, Int] + // def b: Either[String, Int] } object WithComplexReturnTypes { type ReturnType[A] = EitherT[Future, String, A] @@ -71,10 +75,27 @@ trait ProcessorSpec extends PoppetSpec { try { compilesAssert fail("Compilation was successful") - } catch { case e: TestFailedException => - val messages = (message :: alternativeMessages.toList).map(m => s""""$m"""") - val candidateMessage = messages.find(e.getMessage.contains(_)).getOrElse(messages.head) - assert(e.getMessage().contains(candidateMessage)) + } catch { + case e: TestFailedException => + val messages = (message :: alternativeMessages.toList).map(m => s""""$m"""") + val candidateMessage = messages.find(e.getMessage.contains(_)).getOrElse(messages.head) + assert(e.getMessage().contains(candidateMessage)) + } + } + + def assertCompilationErrorMessagePattern( + compilesAssert: => Assertion, + pattern: Regex + )(implicit + pos: Position + ): Assertion = { + try { + compilesAssert + fail("Compilation was successful") + } catch { + case e: TestFailedException => + val finalPattern = s"""[^"]*"${pattern.regex}"[^"]*""" + assert(e.getMessage().matches(finalPattern), s"; `${e.getMessage()}` doesn't match `$finalPattern`") } } } diff --git a/core/test/src/poppet/provider/ProviderProcessorSpec.scala b/core/test/src/poppet/provider/ProviderProcessorSpec.scala index d78ba30..d592912 100644 --- a/core/test/src/poppet/provider/ProviderProcessorSpec.scala +++ b/core/test/src/poppet/provider/ProviderProcessorSpec.scala @@ -34,6 +34,7 @@ class ProviderProcessorSpec extends AsyncFreeSpec with ProcessorSpec { implicit val c3: Codec[List[String], String] = a => Right(a.toString) implicit val cp0: Codec[String, Boolean] = a => Right(a.toBoolean) implicit val cp1: Codec[String, Option[Boolean]] = a => Right(Option(a.toBoolean)) + implicit val cp2: Codec[String, Seq[Boolean]] = a => Right(a.split(",").map(_.toBoolean)) "when has id data kind" - { "for methods with different arguments number" in { @@ -106,9 +107,22 @@ class ProviderProcessorSpec extends AsyncFreeSpec with ProcessorSpec { assert(p(1).service == "poppet.core.ProcessorSpec.WithDefaultArguments" && p(1).name == "a1" && p(1).arguments == List("b0", "b1") && p(1).f(Map("b0" -> "true", "b1" -> "false")) == "List(1, 0)") - assert(p(2).service == "poppet.core.ProcessorSpec.WithDefaultArguments" + assert( + p(2).service == "poppet.core.ProcessorSpec.WithDefaultArguments" && p(2).name == "a2" && p(2).arguments == List("b0", "b1", "b2", "b3") - && p(2).f(Map("b0" -> "true", "b1" -> "true", "b2" -> "true", "b3" -> "true")) == "SimpleDto(4)") + && p(2).f(Map("b0" -> "true", "b1" -> "true", "b2" -> "true", "b3" -> "true")) == "SimpleDto(4)" + ) + } + "for methods with varargs" in { + val t = new WithVarargs { + override def a0(a: Boolean*): Int = a.map(_.toInt).sum + } + + val p = ProviderProcessor[Id, String, WithVarargs].apply(t, FailureHandler.throwing) + + assert(p(0).service == "poppet.core.ProcessorSpec.WithVarargs" + && p(0).name == "a0" && p(0).arguments == List("b") + && p(0).f(Map("b" -> "false,true")) == "1") } "for traits with generic hierarchy" in { val t: WithParentWithParameters = new WithParentWithParameters { @@ -137,15 +151,18 @@ class ProviderProcessorSpec extends AsyncFreeSpec with ProcessorSpec { p(0).f(Map.empty).map(result => assert(p(0).service == "poppet.core.ProcessorSpec.Simple" && p(0).name == "a0" && p(0).arguments == List.empty - && result == "0")) + && result == "0") + ) p(1).f(Map.empty).map(result => assert(p(1).service == "poppet.core.ProcessorSpec.Simple" && p(1).name == "a00" && p(1).arguments == List.empty - && result == "List(0)")) + && result == "List(0)") + ) p(2).f(Map("b" -> "true")).map(result => assert(p(2).service == "poppet.core.ProcessorSpec.Simple" && p(2).name == "a1" && p(2).arguments == List("b") - && result == "1")) + && result == "1") + ) p(3).f(Map("b0" -> "true", "b1" -> "true")).map(result => assert(p(3).service == "poppet.core.ProcessorSpec.Simple" && p(3).name == "a2" && p(3).arguments == List("b0", "b1") @@ -214,7 +231,7 @@ class ProviderProcessorSpec extends AsyncFreeSpec with ProcessorSpec { "for trait with conflicting methods" in { assertCompilationErrorMessage( assertCompiles("""ProviderProcessor[Id, String, WithConflictedMethods]"""), - "Use unique argument name lists for overloaded methods.", + "Use unique argument name lists for overloaded methods." ) } "for trait with abstract type" in { @@ -225,102 +242,95 @@ class ProviderProcessorSpec extends AsyncFreeSpec with ProcessorSpec { ) } "for valid trait without simple codec" in { - assertCompilationErrorMessage( + assertCompilationErrorMessagePattern( assertCompiles("""ProviderProcessor[Id, String, Simple]"""), - "Unable to convert Int to cats.Id[String]. Try to provide poppet.Codec[Int,String].", - "Unable to convert scala.Int to cats.Id[scala.Predef.String]. " + - "Try to provide poppet.Codec[scala.Int,scala.Predef.String].", + ("Unable to convert (scala.)?Int to " + + "((cats.)?Id\\[(scala.Predef.|java.lang.)?String]|(scala.Predef.|java.lang.)?String). " + + "Try to provide poppet.Codec\\[(scala.)?Int,(scala.Predef.|java.lang.)?String].").r ) } "for valid trait without codec for type with argument" in { implicit val c0: Codec[Int, String] = a => Right(a.toString) - assertCompilationErrorMessage( + assertCompilationErrorMessagePattern( assertCompiles("""ProviderProcessor[Id, String, Simple]"""), - "Unable to convert List[Int] to cats.Id[String]. " + - "Try to provide poppet.Codec[List[Int],String] or poppet.CodecK[List,cats.Id].", - "Unable to convert scala.collection.immutable.List[scala.Int] to cats.Id[scala.Predef.String]. " + - "Try to provide poppet.Codec[scala.collection.immutable.List[scala.Int],scala.Predef.String] or " + - "poppet.CodecK[scala.collection.immutable.List,cats.Id].", + ("Unable to convert (scala.collection.immutable.)?List\\[(scala.)?Int] to " + + "((cats.)?Id\\[(scala.Predef.|java.lang.)?String]|(scala.Predef.|java.lang.)?String). " + + "Try to provide poppet.Codec\\[(scala.collection.immutable.)?List\\[(scala.)?Int],(scala.Predef.|java.lang.)?String] " + + "or poppet.CodecK\\[(scala.collection.immutable.)?List,(\\[A\\])?(cats.)?Id(\\[A\\])?].").r ) } "for valid trait without codec for simple type with explicit Id kind" in { implicit val c0: Codec[Int, String] = a => Right(a.toString) implicit val c1: Codec[List[Int], String] = a => Right(a.toString) implicit val c2: Codec[SimpleDto, String] = a => Right(a.toString) - assertCompilationErrorMessage( + assertCompilationErrorMessagePattern( assertCompiles("""ProviderProcessor[Id, String, Simple]"""), - "Unable to convert cats.Id[List[String]] to cats.Id[String]. " + - "Try to provide poppet.Codec[List[String],String].", - "Unable to convert cats.Id[scala.collection.immutable.List[scala.Predef.String]] to cats.Id[scala.Predef.String]. " + - "Try to provide poppet.Codec[scala.collection.immutable.List[scala.Predef.String],scala.Predef.String].", + ("Unable to convert " + + "(cats.)?Id\\[(scala.collection.immutable.)?List\\[(scala.Predef.|java.lang.)?String]] to " + + "((cats.)?Id\\[(scala.Predef.|java.lang.)?String]|(scala.Predef.|java.lang.)?String). " + + "Try to provide " + + "poppet.Codec\\[(scala.collection.immutable.)?List\\[(scala.Predef.|java.lang.)?String],(scala.Predef.|java.lang.)?String].").r ) } "for valid trait without codec for simple type with Future kind" in { - assertCompilationErrorMessage( + assertCompilationErrorMessagePattern( assertCompiles("""ProviderProcessor[Id, String, WithFutureKind]"""), - "Unable to convert scala.concurrent.Future[Int] to cats.Id[String]. " + - "Try to provide poppet.Codec[scala.concurrent.Future[Int],String] " + - "or poppet.CodecK[scala.concurrent.Future,cats.Id] with poppet.Codec[Int,String].", - "Unable to convert scala.concurrent.Future[scala.Int] to cats.Id[scala.Predef.String]. " + - "Try to provide poppet.Codec[scala.concurrent.Future[scala.Int],scala.Predef.String] " + - "or poppet.CodecK[scala.concurrent.Future,cats.Id] with poppet.Codec[scala.Int,scala.Predef.String].", + ("Unable to convert scala.concurrent.Future\\[(scala.)?Int] to " + + "((cats.)?Id\\[(scala.Predef.|java.lang.)?String]|(scala.Predef.|java.lang.)?String). " + + "Try to provide poppet.Codec\\[scala.concurrent.Future\\[(scala.)?Int],(scala.Predef.|java.lang.)?String] or " + + "poppet.CodecK\\[(\\[\\+T\\])?scala.concurrent.Future(\\[T\\])?,(\\[A\\])?(cats.)?Id(\\[A\\])?] " + + "with poppet.Codec\\[(scala.)?Int,(scala.Predef.|java.lang.)?String].").r ) } "for valid trait without codec for simple type with Future kind, but with codecK" in { implicit val ck = new CodecK[Future, Id] { override def apply[A](a: Future[A]): Id[A] = a.value.get.get } - assertCompilationErrorMessage( + assertCompilationErrorMessagePattern( assertCompiles("""ProviderProcessor[Id, String, WithFutureKind]"""), - "Unable to convert scala.concurrent.Future[Int] to cats.Id[String]. " + - "Try to provide poppet.Codec[Int,String].", - "Unable to convert scala.concurrent.Future[scala.Int] to cats.Id[scala.Predef.String]. " + - "Try to provide poppet.Codec[scala.Int,scala.Predef.String].", + ("Unable to convert scala.concurrent.Future\\[(scala.)?Int] to " + + "((cats.)?Id\\[(scala.Predef.|java.lang.)?String]|(scala.Predef.|java.lang.)?String). " + + "Try to provide poppet.Codec\\[(scala.)?Int,(scala.Predef.|java.lang.)?String].").r ) } "for valid trait without codecK for simple type with Future kind, but with codec" in { implicit val c0: Codec[Int, String] = a => Right(a.toString) - assertCompilationErrorMessage( + assertCompilationErrorMessagePattern( assertCompiles("""ProviderProcessor[Id, String, WithFutureKind]"""), - "Unable to convert scala.concurrent.Future[Int] to cats.Id[String]. " + - "Try to provide poppet.Codec[scala.concurrent.Future[Int],String] " + - "or poppet.CodecK[scala.concurrent.Future,cats.Id].", - "Unable to convert scala.concurrent.Future[scala.Int] to cats.Id[scala.Predef.String]. " + - "Try to provide poppet.Codec[scala.concurrent.Future[scala.Int],scala.Predef.String] " + - "or poppet.CodecK[scala.concurrent.Future,cats.Id].", + ("Unable to convert scala.concurrent.Future\\[(scala.)?Int] to " + + "((cats.)?Id\\[(scala.Predef.|java.lang.)?String]|(scala.Predef.|java.lang.)?String). " + + "Try to provide poppet.Codec\\[scala.concurrent.Future\\[(scala.)?Int],(scala.Predef.|java.lang.)?String] or " + + "poppet.CodecK\\[(\\[\\+T\\])?scala.concurrent.Future(\\[T\\])?,(\\[A\\])?(cats.)?Id(\\[A\\])?].").r ) } } "when has Future data kind" - { "for valid trait without simple codec" in { - assertCompilationErrorMessage( + assertCompilationErrorMessagePattern( assertCompiles("""ProviderProcessor[Future, String, Simple]"""), - "Unable to convert Int to scala.concurrent.Future[String]. " + - "Try to provide poppet.CodecK[cats.Id,scala.concurrent.Future] with poppet.Codec[Int,String].", - "Unable to convert scala.Int to scala.concurrent.Future[scala.Predef.String]. " + - "Try to provide poppet.CodecK[cats.Id,scala.concurrent.Future] with poppet.Codec[scala.Int,scala.Predef.String].", + ("Unable to convert (scala.)?Int to scala.concurrent.Future\\[(scala.Predef.|java.lang.)?String]. " + + "Try to provide " + + "poppet.CodecK\\[(\\[A\\])?(cats.)?Id(\\[A\\])?,(\\[\\+T\\])?scala.concurrent.Future(\\[T\\])?] " + + "with poppet.Codec\\[(scala.)?Int,(scala.Predef.|java.lang.)?String].").r ) } "for valid trait without simple codec, but with codecK" in { implicit val ck = new CodecK[Id, Future] { override def apply[A](a: Id[A]): Future[A] = Future.successful(a) } - assertCompilationErrorMessage( + assertCompilationErrorMessagePattern( assertCompiles("""ProviderProcessor[Future, String, Simple]"""), - "Unable to convert Int to scala.concurrent.Future[String]. " + - "Try to provide poppet.Codec[Int,String].", - "Unable to convert scala.Int to scala.concurrent.Future[scala.Predef.String]. " + - "Try to provide poppet.Codec[scala.Int,scala.Predef.String].", + ("Unable to convert (scala.)?Int to scala.concurrent.Future\\[(scala.Predef.|java.lang.)?String]. " + + "Try to provide poppet.Codec\\[(scala.)?Int,(scala.Predef.|java.lang.)?String].").r ) } "for valid trait without simple codecK, but with codec" in { implicit val c0: Codec[Int, String] = a => Right(a.toString) - assertCompilationErrorMessage( + assertCompilationErrorMessagePattern( assertCompiles("""ProviderProcessor[Future, String, Simple]"""), - "Unable to convert Int to scala.concurrent.Future[String]. " + - "Try to provide poppet.CodecK[cats.Id,scala.concurrent.Future].", - "Unable to convert scala.Int to scala.concurrent.Future[scala.Predef.String]. " + - "Try to provide poppet.CodecK[cats.Id,scala.concurrent.Future].", + ("Unable to convert (scala.)?Int to scala.concurrent.Future\\[(scala.Predef.|java.lang.)?String]. " + + "Try to provide " + + "poppet.CodecK\\[(\\[A\\])?(cats.)?Id(\\[A\\])?,(\\[\\+T\\])?scala.concurrent.Future(\\[T\\])?].").r ) } "for valid trait without codec for type with argument" in { @@ -328,13 +338,14 @@ class ProviderProcessorSpec extends AsyncFreeSpec with ProcessorSpec { override def apply[A](a: Id[A]): Future[A] = Future.successful(a) } implicit val c0: Codec[Int, String] = a => Right(a.toString) - assertCompilationErrorMessage( + assertCompilationErrorMessagePattern( assertCompiles("""ProviderProcessor[Future, String, Simple]"""), - "Unable to convert List[Int] to scala.concurrent.Future[String]. " + - "Try to provide poppet.Codec[List[Int],String] or poppet.CodecK[List,scala.concurrent.Future].", - "Unable to convert scala.collection.immutable.List[scala.Int] to scala.concurrent.Future[scala.Predef.String]. " + - "Try to provide poppet.Codec[scala.collection.immutable.List[scala.Int],scala.Predef.String] or " + - "poppet.CodecK[scala.collection.immutable.List,scala.concurrent.Future].", + ("Unable to convert (scala.collection.immutable.)?List\\[(scala.)?Int] to " + + "scala.concurrent.Future\\[(scala.Predef.|java.lang.)?String]. " + + "Try to provide " + + "poppet.Codec\\[(scala.collection.immutable.)?List\\[(scala.)?Int],(scala.Predef.|java.lang.)?String] " + + "or " + + "poppet.CodecK\\[(scala.collection.immutable.)?List,(\\[\\+T\\])?scala.concurrent.Future(\\[T\\])?].").r ) } "for valid trait without codec for simple type with explicit Id kind" in { @@ -344,15 +355,16 @@ class ProviderProcessorSpec extends AsyncFreeSpec with ProcessorSpec { implicit val c0: Codec[Int, String] = a => Right(a.toString) implicit val c1: Codec[List[Int], String] = a => Right(a.toString) implicit val c2: Codec[SimpleDto, String] = a => Right(a.toString) - assertCompilationErrorMessage( + assertCompilationErrorMessagePattern( assertCompiles("""ProviderProcessor[Future, String, Simple]"""), - "Unable to convert cats.Id[List[String]] to scala.concurrent.Future[String]. " + - "Try to provide poppet.Codec[List[String],String].", - "Unable to convert cats.Id[scala.collection.immutable.List[scala.Predef.String]] to scala.concurrent.Future[scala.Predef.String]. " + - "Try to provide poppet.Codec[scala.collection.immutable.List[scala.Predef.String],scala.Predef.String].", + ("Unable to convert " + + "(cats.)?Id\\[(scala.collection.immutable.)?List\\[(scala.Predef.|java.lang.)?String]] to " + + "scala.concurrent.Future\\[(scala.Predef.|java.lang.)?String]. " + + "Try to provide " + + "poppet.Codec\\[(scala.collection.immutable.)?List\\[(scala.Predef.|java.lang.)?String],(scala.Predef.|java.lang.)?String].").r ) } } } } -} \ No newline at end of file +}