From dfd12212851a3ba0480e597a1dd0c0ea7c9c40d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pepe=20Garc=C3=ADa?= Date: Fri, 27 Jul 2018 10:50:23 +0200 Subject: [PATCH 01/13] migrate protobuf to droste --- src/main/scala/protobuf/util.scala | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/src/main/scala/protobuf/util.scala b/src/main/scala/protobuf/util.scala index 5d7ed17f..dd1b40c2 100644 --- a/src/main/scala/protobuf/util.scala +++ b/src/main/scala/protobuf/util.scala @@ -17,8 +17,7 @@ package skeuomorph package protobuf -import turtles._ -import turtles.implicits._ +import qq.droste._ object util { @@ -26,7 +25,7 @@ object util { def printOption(o: Option): String = s"${o.name} = ${o.value}" - def render: Algebra[Schema, String] = { + def render: Algebra[Schema, String] = Algebra { case TDouble() => "double" case TFloat() => "float" case TInt32() => "int32" @@ -105,15 +104,18 @@ message $name { * " * }}} */ - def searchRequest[T](implicit T: Corecursive.Aux[T, Schema]): T = - TMessage[T]( - "SearchRequest", - List( - Field("query", TRequired[T](TString[T]().embed).embed, 1, Nil), - Field("page_number", TOptional[T](TInt32[T]().embed).embed, 2, Nil), - Field("results_per_page", TOptional[T](TInt32[T]().embed).embed, 3, List(Option("default", "10"))), - Field("corpus", TOptional[T](TNamedType[T]("Corpus").embed).embed, 4, List(Option("default", "UNIVERSAL"))) - ), - Nil - ).embed + def searchRequest[T](implicit T: Embed[Schema, T]): T = { + val embed: Schema[T] => T = T.algebra.run + embed( + TMessage[T]( + "SearchRequest", + List( + Field("query", embed(TRequired[T](embed(TString[T]()))), 1, Nil), + Field("page_number", embed(TOptional[T](embed(TInt32[T]()))), 2, Nil), + Field("results_per_page", embed(TOptional[T](embed(TInt32[T]()))), 3, List(Option("default", "10"))), + Field("corpus", embed(TOptional[T](embed(TNamedType[T]("Corpus")))), 4, List(Option("default", "UNIVERSAL"))) + ), + Nil + )) + } } From 4eb76390c97788a1e78ac5d579d7213a3740f0bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pepe=20Garc=C3=ADa?= Date: Fri, 27 Jul 2018 11:55:08 +0200 Subject: [PATCH 02/13] WIP: migrate freestyle to droste --- src/main/scala/freestyle/Service.scala | 72 +++++++++++--------------- src/main/scala/freestyle/util.scala | 37 +++++++------ 2 files changed, 48 insertions(+), 61 deletions(-) diff --git a/src/main/scala/freestyle/Service.scala b/src/main/scala/freestyle/Service.scala index bc4ccd2b..65b0ff45 100644 --- a/src/main/scala/freestyle/Service.scala +++ b/src/main/scala/freestyle/Service.scala @@ -17,54 +17,42 @@ package skeuomorph package freestyle -import turtles._ -import turtles.implicits._ +//import qq.droste._ -/** - * [[Service]] describes a service representation in freestyle-rpc. - * - * @see http://frees.io/docs/rpc/idl-generation - */ case class Service[T](pkg: String, name: String, declarations: List[T], operations: List[Service.Operation[T]]) object Service { - /** - * Each one of the endpoints of the service - */ case class Operation[T](name: String, request: T, response: T) - /** - * Optimization to kill recursion in nested product/sum types while - * rendering. - */ - def namedTypes[T](t: T)(implicit T: Birecursive.Aux[T, Schema]): T = t.project match { - case Schema.TProduct(name, _) => Schema.TNamedType[T](name).embed - case Schema.TSum(name, _) => Schema.TNamedType[T](name).embed - case other => other.embed - } - - /** - * Render a [[Service]] to its String representation - */ - def render[T](service: Service[T])(implicit T: Birecursive.Aux[T, Schema]): String = { - val printDeclarations = service.declarations.map(_.cata(util.render)).mkString("\n") - val printOperations = service.operations.map { op => - val printRequest = op.request.transCataT(namedTypes).cata(util.render) - val printResponse = op.response.transCataT(namedTypes).cata(util.render) - - s"def ${op.name}(req: $printRequest): F[$printResponse]" - } mkString ("\n ") - val printService = s""" -@service trait ${service.name}[F[_]] { - $printOperations -} -""" - s""" -package ${service.pkg} -$printDeclarations -$printService -""" - } + // private def trans[T: Project[Schema, ?]]: Trans[Schema, Schema, T] = Trans { fa => + // fa match { + // case Schema.TProduct(name, _) => Schema.TNamedType[T](name) + // case Schema.TSum(name, _) => Schema.TNamedType[T](name) + // case other => other + // } + // } + +// private def _namedTypes[T: Basis[Schema, ?]]: T => T = scheme.cata(trans[T].algebra) + +// def renderService[T](service: Service[T])(implicit T: Basis[Schema, T]): String = { +// val printDeclarations = service.declarations.map(scheme.cata(render)).mkString("\n") +// val printOperations = service.operations.map { op => +// val printRequest = Simplify.namedTypes(op.request).cata(render) +// val printResponse = Simplify.namedTypes(op.response).cata(render) + +// s"def ${op.name}(req: $printRequest): F[$printResponse]" +// } mkString ("\n ") +// val printService = s""" +// @service trait ${service.name}[F[_]] { +// $printOperations +// } +// """ +// s""" +// package ${service.pkg} +// $printDeclarations +// $printService +// """ +// } } diff --git a/src/main/scala/freestyle/util.scala b/src/main/scala/freestyle/util.scala index 4e8e8bcf..dccaa461 100644 --- a/src/main/scala/freestyle/util.scala +++ b/src/main/scala/freestyle/util.scala @@ -19,8 +19,8 @@ package freestyle import protobuf.{Schema => ProtoSchema} import avro.{Schema => AvroSchema} -import turtles._ -import turtles.implicits._ + +import qq.droste._ object util { @@ -29,7 +29,7 @@ object util { /** * transform Protobuf schema into Freestyle schema */ - def transformProto[A]: ProtoSchema[A] => Schema[A] = { + def transformProto[A]: Trans[ProtoSchema, Schema, A] = Trans { case ProtoSchema.TDouble() => TDouble() case ProtoSchema.TFloat() => TFloat() case ProtoSchema.TInt32() => TInt() @@ -53,7 +53,7 @@ object util { case ProtoSchema.TMessage(name, fields, _) => TProduct(name, fields.map(f => Field(f.name, f.tpe))) } - def transformAvro[A]: AvroSchema[A] => Schema[A] = { + def transformAvro[A]: Trans[AvroSchema, Schema, A] = Trans { case AvroSchema.TNull() => TNull() case AvroSchema.TBoolean() => TBoolean() case AvroSchema.TInt() => TInt() @@ -90,21 +90,20 @@ object util { * case class Product(field1: String, field2: OtherField) * }}} */ - def namedTypes[T](t: T)(implicit T: Birecursive.Aux[T, Schema]): T = - t.project match { - case TProduct(name, fields) => - TProduct[T]( - name, - fields.map { f: Field[T] => - f.copy(tpe = f.tpe.transCataT(_.project match { - case TProduct(name, _) => TNamedType[T](name).embed - case TSum(name, _) => TNamedType[T](name).embed - case other => other.embed - })) - } - ).embed - case other => other.embed - } + def namedTypes[T]: Trans[Schema, Schema, T] = Trans { + case TProduct(name, fields) => + TProduct[T]( + name, + fields.map { f: Field[T] => + f.copy(tpe = f.tpe.transCataT(_.project match { + case TProduct(name, _) => TNamedType[T](name) + case TSum(name, _) => TNamedType[T](name) + case other => other + })) + } + ) + case other => other + } def render: Algebra[Schema, String] = { case TNull() => "Null" From 9926267cded2a1462f9e7763127d8b5b80aaa081 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pepe=20Garc=C3=ADa?= Date: Tue, 31 Jul 2018 11:32:46 +0200 Subject: [PATCH 03/13] migrate to droste --- src/main/scala/freestyle/util.scala | 84 +++++++++++++++++------------ 1 file changed, 49 insertions(+), 35 deletions(-) diff --git a/src/main/scala/freestyle/util.scala b/src/main/scala/freestyle/util.scala index dccaa461..18a09548 100644 --- a/src/main/scala/freestyle/util.scala +++ b/src/main/scala/freestyle/util.scala @@ -74,38 +74,49 @@ object util { } /** - * micro-optimization to convert types from fields in a product to - * NamedTypes. - * - * Without this optimization, printing a product containing fields - * of other products would end up with something like: - * - * {{{ - * case class Product(field1: String, field2: case class OtherField()) - * }}} - * - * With it, we cut recursion in messages, to leave only type names: - * - * {{{ - * case class Product(field1: String, field2: OtherField) - * }}} + * Optimize object contains same schema transformations */ - def namedTypes[T]: Trans[Schema, Schema, T] = Trans { - case TProduct(name, fields) => - TProduct[T]( - name, - fields.map { f: Field[T] => - f.copy(tpe = f.tpe.transCataT(_.project match { - case TProduct(name, _) => TNamedType[T](name) - case TSum(name, _) => TNamedType[T](name) - case other => other - })) - } - ) - case other => other + object Optimize { + + /** + * micro-optimization to convert types from fields in a product to + * NamedTypes. + * + * Without this optimization, printing a product containing fields + * of other products would end up with something like: + * + * {{{ + * case class Product(field1: String, field2: case class OtherField()) + * }}} + * + * With it, we cut recursion in messages, to leave only type names: + * + * {{{ + * case class Product(field1: String, field2: OtherField) + * }}} + */ + def nestedNamedTypesTrans[T](implicit T: Basis[Schema, T]): Trans[Schema, Schema, T] = Trans { + case TProduct(name, fields) => + TProduct[T]( + name, + fields.map { f: Field[T] => + f.copy(tpe = namedTypes(T)(f.tpe)) + } + ) + case other => other + } + + def namedTypesTrans[T]: Trans[Schema, Schema, T] = Trans { + case TProduct(name, _) => TNamedType[T](name) + case TSum(name, _) => TNamedType[T](name) + case other => other + } + + def namedTypes[T: Basis[Schema, ?]]: T => T = scheme.cata(namedTypesTrans.algebra) + def nestedNamedTypes[T: Basis[Schema, ?]]: T => T = scheme.cata(nestedNamedTypesTrans.algebra) } - def render: Algebra[Schema, String] = { + def render: Algebra[Schema, String] = Algebra { case TNull() => "Null" case TDouble() => "Double" case TFloat() => "Float" @@ -139,19 +150,22 @@ object $name { /** * create a [[skeuomorph.freestyle.Service]] from a [[skeuomorph.avro.Protocol]] */ - def fromAvroProtocol[T, U](proto: AvroSchema.Protocol[T])( - implicit T: Recursive.Aux[T, avro.Schema], - U: Corecursive.Aux[U, Schema]): Service[U] = + def fromAvroProtocol[T, U]( + proto: AvroSchema.Protocol[T])(implicit T: Basis[avro.Schema, T], U: Basis[Schema, U]): Service[U] = { + + val toFreestyle: T => U = scheme.cata(transformAvro[U].algebra) + Service( proto.namespace.fold("")(identity), proto.name, - proto.types.map(t => t.transCata[U](transformAvro)), + proto.types.map(toFreestyle), proto.messages.map( msg => Service.Operation( msg.name, - msg.request.transCata[U](transformAvro), - msg.response.transCata[U](transformAvro) + toFreestyle(msg.request), + toFreestyle(msg.response) )) ) + } } From 0d85733d013d085603e7e9e6715e6dc30de6b4a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pepe=20Garc=C3=ADa?= Date: Tue, 31 Jul 2018 17:22:39 +0200 Subject: [PATCH 04/13] migrate tests to droste too --- build.sbt | 2 +- src/main/scala/avro/util.scala | 13 +++++++------ src/test/scala/avro/AvroSpec.scala | 12 +++++------- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/build.sbt b/build.sbt index 159b7661..42395f5c 100644 --- a/build.sbt +++ b/build.sbt @@ -63,7 +63,7 @@ lazy val commonSettings = Seq( ThisBuild / scalacOptions -= "-Xplugin-require:macroparadise", libraryDependencies ++= Seq( %%("cats-core"), - "org.technomadic" %% "turtles-core" % "0.1.0", + "io.higherkindness" %% "droste-core" % "0.3.1-SNAPSHOT", "org.apache.avro" % "avro" % "1.8.2", %%("specs2-core") % Test, %%("specs2-scalacheck") % Test, diff --git a/src/main/scala/avro/util.scala b/src/main/scala/avro/util.scala index ef2ae22e..6f236ce7 100644 --- a/src/main/scala/avro/util.scala +++ b/src/main/scala/avro/util.scala @@ -23,8 +23,7 @@ import org.apache.avro.Schema.{Type => AvroType} import scala.collection.JavaConverters._ import cats.data.NonEmptyList -import turtles._ -import turtles.implicits._ +import qq.droste._ object util { @@ -44,15 +43,17 @@ object util { avroF.schema ) - def fromProto[T](proto: AvroProtocol)(implicit C: Corecursive.Aux[T, Schema]): Protocol[T] = { + def fromProto[T](proto: AvroProtocol)(implicit T: Embed[Schema, T]): Protocol[T] = { + val toFreestyle: AvroSchema => T = scheme.ana(fromAvro) + Protocol( proto.getName, Option(proto.getNamespace), - proto.getTypes.asScala.toList.map(_.ana[T](fromAvro)), + proto.getTypes.asScala.toList.map(toFreestyle), proto.getMessages.asScala .map({ case (_, message) => - Message[T](message.getName, message.getRequest.ana[T](fromAvro), message.getResponse.ana[T](fromAvro)) + Message[T](message.getName, toFreestyle(message.getRequest), toFreestyle(message.getResponse)) }) .toList ) @@ -61,7 +62,7 @@ object util { /** * Convert [[org.apache.avro.Schema]] to [[skeuomorph.avro.AvroSchema]] */ - def fromAvro: Coalgebra[Schema, AvroSchema] = { sch => + def fromAvro: Coalgebra[Schema, AvroSchema] = Coalgebra { sch => sch.getType match { case AvroType.STRING => Schema.TString() case AvroType.BOOLEAN => Schema.TBoolean() diff --git a/src/test/scala/avro/AvroSpec.scala b/src/test/scala/avro/AvroSpec.scala index 62d0f311..4485fde9 100644 --- a/src/test/scala/avro/AvroSpec.scala +++ b/src/test/scala/avro/AvroSpec.scala @@ -23,9 +23,7 @@ import org.specs2._ import org.scalacheck._ import org.apache.avro.{Schema => AvroSchema} -import turtles.Algebra -import turtles.data.Mu -import turtles.implicits._ +import qq.droste._ class AvroSpec extends Specification with ScalaCheck { @@ -40,14 +38,14 @@ class AvroSpec extends Specification with ScalaCheck { """ def convertSchema = Prop.forAll { (schema: AvroSchema) => - schema - .ana[Mu[Schema]](util.fromAvro) - .cata(checkSchema(schema)) + val test = scheme.hylo(checkSchema(schema).run, util.fromAvro.run) + + test(schema) } def convertProtocol = todo - def checkSchema(sch: AvroSchema): Algebra[Schema, Boolean] = { + def checkSchema(sch: AvroSchema): Algebra[Schema, Boolean] = Algebra { case Schema.TNull() => sch.getType should_== AvroSchema.Type.NULL case Schema.TBoolean() => sch.getType should_== AvroSchema.Type.BOOLEAN case Schema.TInt() => sch.getType should_== AvroSchema.Type.INT From 554bda299b362e656504706d6354aa7285cc2e10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pepe=20Garc=C3=ADa?= Date: Tue, 31 Jul 2018 17:23:40 +0200 Subject: [PATCH 05/13] deleted dummy function from proto utils --- src/main/scala/protobuf/util.scala | 39 ------------------------------ 1 file changed, 39 deletions(-) diff --git a/src/main/scala/protobuf/util.scala b/src/main/scala/protobuf/util.scala index dd1b40c2..0d52f0b3 100644 --- a/src/main/scala/protobuf/util.scala +++ b/src/main/scala/protobuf/util.scala @@ -79,43 +79,4 @@ message $name { } """ } - - /** - * {{{ - * scala> import skeuomorph._ - * import skeuomorph._ - * - * scala> import turtles.data.Mu - * import turtles.data.Mu - * - * scala> import turtles.implicits._ - * import turtles.implicits._ - * - * scala> protobuf.util.searchRequest[Mu[protobuf.Schema]].cata(protobuf.util.render) - * res0: String = - * " - * message SearchRequest { - * - * required string query = 1; - * optional int32 page_number = 2; - * optional int32 results_per_page = 3 [default = 10]; - * optional Corpus corpus = 3 [default = UNIVERSAL]; - * } - * " - * }}} - */ - def searchRequest[T](implicit T: Embed[Schema, T]): T = { - val embed: Schema[T] => T = T.algebra.run - embed( - TMessage[T]( - "SearchRequest", - List( - Field("query", embed(TRequired[T](embed(TString[T]()))), 1, Nil), - Field("page_number", embed(TOptional[T](embed(TInt32[T]()))), 2, Nil), - Field("results_per_page", embed(TOptional[T](embed(TInt32[T]()))), 3, List(Option("default", "10"))), - Field("corpus", embed(TOptional[T](embed(TNamedType[T]("Corpus")))), 4, List(Option("default", "UNIVERSAL"))) - ), - Nil - )) - } } From 020c739ec62122ca3e09823b6d1f8aea2fcab4b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pepe=20Garc=C3=ADa?= Date: Tue, 31 Jul 2018 20:40:14 +0200 Subject: [PATCH 06/13] migrate Service to droste --- src/main/scala/freestyle/Service.scala | 57 ++++++++++++-------------- 1 file changed, 27 insertions(+), 30 deletions(-) diff --git a/src/main/scala/freestyle/Service.scala b/src/main/scala/freestyle/Service.scala index 65b0ff45..320a4d3d 100644 --- a/src/main/scala/freestyle/Service.scala +++ b/src/main/scala/freestyle/Service.scala @@ -17,7 +17,11 @@ package skeuomorph package freestyle -//import qq.droste._ +import util.Optimize.namedTypes + +import cats.instances.function._ +import cats.syntax.compose._ +import qq.droste._ case class Service[T](pkg: String, name: String, declarations: List[T], operations: List[Service.Operation[T]]) @@ -25,34 +29,27 @@ object Service { case class Operation[T](name: String, request: T, response: T) - // private def trans[T: Project[Schema, ?]]: Trans[Schema, Schema, T] = Trans { fa => - // fa match { - // case Schema.TProduct(name, _) => Schema.TNamedType[T](name) - // case Schema.TSum(name, _) => Schema.TNamedType[T](name) - // case other => other - // } - // } - -// private def _namedTypes[T: Basis[Schema, ?]]: T => T = scheme.cata(trans[T].algebra) - -// def renderService[T](service: Service[T])(implicit T: Basis[Schema, T]): String = { -// val printDeclarations = service.declarations.map(scheme.cata(render)).mkString("\n") -// val printOperations = service.operations.map { op => -// val printRequest = Simplify.namedTypes(op.request).cata(render) -// val printResponse = Simplify.namedTypes(op.response).cata(render) - -// s"def ${op.name}(req: $printRequest): F[$printResponse]" -// } mkString ("\n ") -// val printService = s""" -// @service trait ${service.name}[F[_]] { -// $printOperations -// } -// """ -// s""" -// package ${service.pkg} -// $printDeclarations -// $printService -// """ -// } + def render[T](service: Service[T])(implicit T: Basis[Schema, T]): String = { + val renderSchema: T => String = scheme.cata(util.render) + val optimizeAndPrint = namedTypes >>> renderSchema + + val printDeclarations = service.declarations.map(renderSchema).mkString("\n") + val printOperations = service.operations.map { op => + val printRequest = optimizeAndPrint(op.request) + val printResponse = optimizeAndPrint(op.response) + + s"def ${op.name}(req: $printRequest): F[$printResponse]" + } mkString ("\n ") + val printService = s""" +@service trait ${service.name}[F[_]] { + $printOperations +} +""" + s""" +package ${service.pkg} +$printDeclarations +$printService +""" + } } From 3aa056af205fac15418bf5542760857b4bce0c0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pepe=20Garc=C3=ADa?= Date: Wed, 1 Aug 2018 09:45:03 +0200 Subject: [PATCH 07/13] migrate docs to droste --- README.md | 21 ++++++++---- build.sbt | 2 +- docs/src/main/tut/optimizations.md | 54 +++++++++++++++++------------- docs/src/main/tut/schemas.md | 9 ----- 4 files changed, 46 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index ba14e2b3..58d5eb96 100644 --- a/README.md +++ b/README.md @@ -47,9 +47,14 @@ libraryDependencies += "io.frees" %% "skeuomorph" % "0.1.0" ```tut import org.apache.avro._ import skeuomorph._ -import turtles._ -import turtles.data.Mu -import turtles.implicits._ +import skeuomorph.freestyle.Transform.transformAvro +import skeuomorph.freestyle.Schema.render +import skeuomorph.avro.util.fromAvro +import qq.droste._ +import qq.droste.data._ +import qq.droste.implicits._ +import cats.implicits._ + val definition = """ { @@ -81,10 +86,12 @@ val definition = """ val schema: Schema = new Schema.Parser().parse(definition) -schema. - ana[Mu[avro.Schema]](avro.util.fromAvro). // org.apache.avro.Schema => skeuomorph.avro.Schema. - transCata[Mu[freestyle.Schema]](freestyle.util.transformAvro). // skeuomorph.avro.Schema => skeuomorph.freestyle.Schema. - cata(freestyle.util.render) // skeuomorph.freestyle.Schema => String +val parseAvroSchema: Schema => Fix[freestyle.Schema] = + scheme.hylo(transformAvro[Fix[freestyle.Schema]].algebra.run, fromAvro.run) +val printSchema: Fix[freestyle.Schema] => String = + scheme.cata(render) + +(parseAvroSchema >>> printSchema)(schema) ``` diff --git a/build.sbt b/build.sbt index 42395f5c..152fc298 100644 --- a/build.sbt +++ b/build.sbt @@ -63,7 +63,7 @@ lazy val commonSettings = Seq( ThisBuild / scalacOptions -= "-Xplugin-require:macroparadise", libraryDependencies ++= Seq( %%("cats-core"), - "io.higherkindness" %% "droste-core" % "0.3.1-SNAPSHOT", + "io.higherkindness" %% "droste-core" % "0.3.1", "org.apache.avro" % "avro" % "1.8.2", %%("specs2-core") % Test, %%("specs2-scalacheck") % Test, diff --git a/docs/src/main/tut/optimizations.md b/docs/src/main/tut/optimizations.md index 78858b1f..c04b44bb 100644 --- a/docs/src/main/tut/optimizations.md +++ b/docs/src/main/tut/optimizations.md @@ -20,9 +20,10 @@ mechanisms to apply that function to the AST correctly. Let's see ## NamedTypes ```tut:invisible -import turtles._ -import turtles.data._ -import turtles.implicits._ +import qq.droste._ +import qq.droste.data._ +import qq.droste.implicits._ + import skeuomorph.freestyle._ import skeuomorph.freestyle.Schema._ ``` @@ -42,32 +43,39 @@ case class Product(field1: String, field2: OtherField) We solve this by substituting nested product types by it's name when they're inside a product themselves. And we do this with the -`namedTypes` combinator: - -```tut:silent -def namedTypes[T](t: T)(implicit T: Birecursive.Aux[T, Schema]): T = - t.project match { - case TProduct(name, fields) => - TProduct[T]( - name, - fields.map { f: Field[T] => - f.copy(tpe = f.tpe.transCataT(_.project match { - case TProduct(name, _) => TNamedType[T](name).embed - case TSum(name, _) => TNamedType[T](name).embed - case other => other.embed - })) - } - ).embed - case other => other.embed - } +`namedTypes` combinator (in `skeuomorph.freestyle.util`): + +```scala +def nestedNamedTypesTrans[T](implicit T: Basis[Schema, T]): Trans[Schema, Schema, T] = Trans { + case TProduct(name, fields) => + TProduct[T]( + name, + fields.map { f: Field[T] => + f.copy(tpe = namedTypes(T)(f.tpe)) + } + ) + case other => other +} + +def namedTypesTrans[T]: Trans[Schema, Schema, T] = Trans { + case TProduct(name, _) => TNamedType[T](name) + case TSum(name, _) => TNamedType[T](name) + case other => other +} + +def namedTypes[T: Basis[Schema, ?]]: T => T = scheme.cata(namedTypesTrans.algebra) +def nestedNamedTypes[T: Basis[Schema, ?]]: T => T = scheme.cata(nestedNamedTypesTrans.algebra) + ``` and then apply the `namedTypes` combinator to the AST: ```tut:invisible -def ast[T](implicit T: Birecursive.Aux[T, Schema]): T = TNull[T]().embed +def ast = Fix(TNull[Fix[Schema]]()) ``` ```tut -ast[Mu[Schema]].transCataT(namedTypes) +val optimization = Optimize.namedTypes[Fix[Schema]] + +optimization(ast) ``` diff --git a/docs/src/main/tut/schemas.md b/docs/src/main/tut/schemas.md index 93c6b8f8..4e953c0c 100644 --- a/docs/src/main/tut/schemas.md +++ b/docs/src/main/tut/schemas.md @@ -6,15 +6,6 @@ position: 1 # Schemas -> Examples in this page expects you to have the following imports in -> place: - -```tut:silent -import turtles.data._ -import turtles.implicits._ -import skeuomorph._ -``` - Currently in skeuomorph there are schemas defined for different cases: - [Avro][] From c081e64efabd8f7bfc0669284a9aca7cb68ec30d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pepe=20Garc=C3=ADa?= Date: Wed, 1 Aug 2018 09:45:20 +0200 Subject: [PATCH 08/13] divide freestyle.util into different packages --- src/main/scala/freestyle/Optimize.scala | 64 +++++++++++ src/main/scala/freestyle/Service.scala | 27 ++++- .../freestyle/{util.scala => Transform.scala} | 100 +----------------- src/main/scala/freestyle/schema.scala | 32 ++++++ 4 files changed, 123 insertions(+), 100 deletions(-) create mode 100644 src/main/scala/freestyle/Optimize.scala rename src/main/scala/freestyle/{util.scala => Transform.scala} (52%) diff --git a/src/main/scala/freestyle/Optimize.scala b/src/main/scala/freestyle/Optimize.scala new file mode 100644 index 00000000..8fd62285 --- /dev/null +++ b/src/main/scala/freestyle/Optimize.scala @@ -0,0 +1,64 @@ +/* + * Copyright 2018 47 Degrees, LLC. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package skeuomorph +package freestyle + +import qq.droste._ +import Schema._ + +/** + * Optimize object contains transformations in same schema + */ +object Optimize { + + /** + * micro-optimization to convert types from fields in a product to + * NamedTypes. + * + * Without this optimization, printing a product containing fields + * of other products would end up with something like: + * + * {{{ + * case class Product(field1: String, field2: case class OtherField()) + * }}} + * + * With it, we cut recursion in messages, to leave only type names: + * + * {{{ + * case class Product(field1: String, field2: OtherField) + * }}} + */ + def nestedNamedTypesTrans[T](implicit T: Basis[Schema, T]): Trans[Schema, Schema, T] = Trans { + case TProduct(name, fields) => + TProduct[T]( + name, + fields.map { f: Field[T] => + f.copy(tpe = namedTypes(T)(f.tpe)) + } + ) + case other => other + } + + def namedTypesTrans[T]: Trans[Schema, Schema, T] = Trans { + case TProduct(name, _) => TNamedType[T](name) + case TSum(name, _) => TNamedType[T](name) + case other => other + } + + def namedTypes[T: Basis[Schema, ?]]: T => T = scheme.cata(namedTypesTrans.algebra) + def nestedNamedTypes[T: Basis[Schema, ?]]: T => T = scheme.cata(nestedNamedTypesTrans.algebra) +} diff --git a/src/main/scala/freestyle/Service.scala b/src/main/scala/freestyle/Service.scala index 320a4d3d..ade2ac06 100644 --- a/src/main/scala/freestyle/Service.scala +++ b/src/main/scala/freestyle/Service.scala @@ -17,7 +17,8 @@ package skeuomorph package freestyle -import util.Optimize.namedTypes +import Optimize.namedTypes +import Transform.transformAvro import cats.instances.function._ import cats.syntax.compose._ @@ -29,8 +30,30 @@ object Service { case class Operation[T](name: String, request: T, response: T) + /** + * create a [[skeuomorph.freestyle.Service]] from a [[skeuomorph.avro.Protocol]] + */ + def fromAvroProtocol[T, U]( + proto: avro.Schema.Protocol[T])(implicit T: Basis[avro.Schema, T], U: Basis[Schema, U]): Service[U] = { + + val toFreestyle: T => U = scheme.cata(transformAvro[U].algebra) + + Service( + proto.namespace.fold("")(identity), + proto.name, + proto.types.map(toFreestyle), + proto.messages.map( + msg => + Service.Operation( + msg.name, + toFreestyle(msg.request), + toFreestyle(msg.response) + )) + ) + } + def render[T](service: Service[T])(implicit T: Basis[Schema, T]): String = { - val renderSchema: T => String = scheme.cata(util.render) + val renderSchema: T => String = scheme.cata(Schema.render) val optimizeAndPrint = namedTypes >>> renderSchema val printDeclarations = service.declarations.map(renderSchema).mkString("\n") diff --git a/src/main/scala/freestyle/util.scala b/src/main/scala/freestyle/Transform.scala similarity index 52% rename from src/main/scala/freestyle/util.scala rename to src/main/scala/freestyle/Transform.scala index 18a09548..4422993c 100644 --- a/src/main/scala/freestyle/util.scala +++ b/src/main/scala/freestyle/Transform.scala @@ -19,10 +19,9 @@ package freestyle import protobuf.{Schema => ProtoSchema} import avro.{Schema => AvroSchema} +import qq.droste.Trans -import qq.droste._ - -object util { +object Transform { import Schema._ @@ -73,99 +72,4 @@ object util { ??? // I don't really know what to do with Fixed... https://avro.apache.org/docs/current/spec.html#Fixed } - /** - * Optimize object contains same schema transformations - */ - object Optimize { - - /** - * micro-optimization to convert types from fields in a product to - * NamedTypes. - * - * Without this optimization, printing a product containing fields - * of other products would end up with something like: - * - * {{{ - * case class Product(field1: String, field2: case class OtherField()) - * }}} - * - * With it, we cut recursion in messages, to leave only type names: - * - * {{{ - * case class Product(field1: String, field2: OtherField) - * }}} - */ - def nestedNamedTypesTrans[T](implicit T: Basis[Schema, T]): Trans[Schema, Schema, T] = Trans { - case TProduct(name, fields) => - TProduct[T]( - name, - fields.map { f: Field[T] => - f.copy(tpe = namedTypes(T)(f.tpe)) - } - ) - case other => other - } - - def namedTypesTrans[T]: Trans[Schema, Schema, T] = Trans { - case TProduct(name, _) => TNamedType[T](name) - case TSum(name, _) => TNamedType[T](name) - case other => other - } - - def namedTypes[T: Basis[Schema, ?]]: T => T = scheme.cata(namedTypesTrans.algebra) - def nestedNamedTypes[T: Basis[Schema, ?]]: T => T = scheme.cata(nestedNamedTypesTrans.algebra) - } - - def render: Algebra[Schema, String] = Algebra { - case TNull() => "Null" - case TDouble() => "Double" - case TFloat() => "Float" - case TInt() => "Int" - case TLong() => "Long" - case TBoolean() => "Boolean" - case TString() => "String" - case TByteArray() => "Array[Byte]" - case TNamedType(name) => name - case TOption(value) => s"Option[$value]" - case TMap(value) => s"Map[String, $value]" - case TList(value) => s"List[$value]" - case TRequired(value) => value - case TCoproduct(invariants) => - invariants.toList.mkString("Cop[", " :: ", ":: TNil]") - case TSum(name, fields) => - val printFields = fields.map(f => s"case object $f extends $name").mkString("\n ") - s""" -sealed trait $name -object $name { - $printFields -} -""" - case TProduct(name, fields) => - val printFields = fields.map(f => s"${f.name}: ${f.tpe}").mkString(", ") - s""" -@message case class $name($printFields) -""" - } - - /** - * create a [[skeuomorph.freestyle.Service]] from a [[skeuomorph.avro.Protocol]] - */ - def fromAvroProtocol[T, U]( - proto: AvroSchema.Protocol[T])(implicit T: Basis[avro.Schema, T], U: Basis[Schema, U]): Service[U] = { - - val toFreestyle: T => U = scheme.cata(transformAvro[U].algebra) - - Service( - proto.namespace.fold("")(identity), - proto.name, - proto.types.map(toFreestyle), - proto.messages.map( - msg => - Service.Operation( - msg.name, - toFreestyle(msg.request), - toFreestyle(msg.response) - )) - ) - } } diff --git a/src/main/scala/freestyle/schema.scala b/src/main/scala/freestyle/schema.scala index fa92efc2..015a2299 100644 --- a/src/main/scala/freestyle/schema.scala +++ b/src/main/scala/freestyle/schema.scala @@ -17,6 +17,7 @@ package skeuomorph package freestyle +import qq.droste.Algebra import cats.Functor import cats.data.NonEmptyList @@ -62,4 +63,35 @@ object Schema { } } + def render: Algebra[Schema, String] = Algebra { + case TNull() => "Null" + case TDouble() => "Double" + case TFloat() => "Float" + case TInt() => "Int" + case TLong() => "Long" + case TBoolean() => "Boolean" + case TString() => "String" + case TByteArray() => "Array[Byte]" + case TNamedType(name) => name + case TOption(value) => s"Option[$value]" + case TMap(value) => s"Map[String, $value]" + case TList(value) => s"List[$value]" + case TRequired(value) => value + case TCoproduct(invariants) => + invariants.toList.mkString("Cop[", " :: ", ":: TNil]") + case TSum(name, fields) => + val printFields = fields.map(f => s"case object $f extends $name").mkString("\n ") + s""" +sealed trait $name +object $name { + $printFields +} +""" + case TProduct(name, fields) => + val printFields = fields.map(f => s"${f.name}: ${f.tpe}").mkString(", ") + s""" +@message case class $name($printFields) +""" + } + } From c9d6412b803bbb7c373d197df6428a0431035db5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pepe=20Garc=C3=ADa?= Date: Wed, 1 Aug 2018 09:59:47 +0200 Subject: [PATCH 09/13] split avro.util --- src/main/scala/avro/Protocol.scala | 53 ++++++++++++ src/main/scala/avro/schema.scala | 75 +++++++++++++++-- src/main/scala/avro/util.scala | 108 ------------------------- src/main/scala/freestyle/Service.scala | 2 +- 4 files changed, 121 insertions(+), 117 deletions(-) create mode 100644 src/main/scala/avro/Protocol.scala delete mode 100644 src/main/scala/avro/util.scala diff --git a/src/main/scala/avro/Protocol.scala b/src/main/scala/avro/Protocol.scala new file mode 100644 index 00000000..19e80813 --- /dev/null +++ b/src/main/scala/avro/Protocol.scala @@ -0,0 +1,53 @@ +/* + * Copyright 2018 47 Degrees, LLC. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package skeuomorph +package avro + +import org.apache.avro.{Schema => AvroSchema, Protocol => AvroProtocol} +import scala.collection.JavaConverters._ + +import qq.droste._ + +case class Protocol[A]( + name: String, + namespace: Option[String], + types: List[A], + messages: List[Protocol.Message[A]] +) + +object Protocol { + import Schema._ + + case class Message[A](name: String, request: A, response: A) + + def fromProto[T](proto: AvroProtocol)(implicit T: Embed[Schema, T]): Protocol[T] = { + val toFreestyle: AvroSchema => T = scheme.ana(fromAvro) + + Protocol( + proto.getName, + Option(proto.getNamespace), + proto.getTypes.asScala.toList.map(toFreestyle), + proto.getMessages.asScala + .map({ + case (_, message) => + Message[T](message.getName, toFreestyle(message.getRequest), toFreestyle(message.getResponse)) + }) + .toList + ) + } + +} diff --git a/src/main/scala/avro/schema.scala b/src/main/scala/avro/schema.scala index 0d0ba836..2070ebbc 100644 --- a/src/main/scala/avro/schema.scala +++ b/src/main/scala/avro/schema.scala @@ -17,20 +17,17 @@ package skeuomorph package avro +import scala.collection.JavaConverters._ + import cats.Functor import cats.data.NonEmptyList +import org.apache.avro.{Schema => AvroSchema} +import org.apache.avro.Schema.{Type => AvroType} +import qq.droste.Coalgebra sealed trait Schema[A] object Schema { - case class Message[A](name: String, request: A, response: A) - case class Protocol[A]( - name: String, - namespace: Option[String], - types: List[A], - messages: List[Message[A]] - ) - sealed trait Order object Order { case object Ascending extends Order @@ -97,4 +94,66 @@ object Schema { case Schema.TFixed(name, namespace, aliases, size) => Schema.TFixed(name, namespace, aliases, size) } } + + /** + * Convert [[org.apache.avro.Schema]] to [[skeuomorph.avro.AvroSchema]] + */ + def fromAvro: Coalgebra[Schema, AvroSchema] = Coalgebra { sch => + sch.getType match { + case AvroType.STRING => Schema.TString() + case AvroType.BOOLEAN => Schema.TBoolean() + case AvroType.BYTES => Schema.TBytes() + case AvroType.DOUBLE => Schema.TDouble() + case AvroType.FLOAT => Schema.TFloat() + case AvroType.INT => Schema.TInt() + case AvroType.LONG => Schema.TLong() + case AvroType.NULL => Schema.TNull() + case AvroType.MAP => Schema.TMap(sch.getValueType) + case AvroType.ARRAY => Schema.TArray(sch.getElementType) + case AvroType.RECORD => + Schema.TRecord( + sch.getName, + Option(sch.getNamespace), + sch.getAliases.asScala.toList, + Option(sch.getDoc), + sch.getFields.asScala.toList.map(field2Field) + ) + case AvroType.ENUM => + val symbols = sch.getEnumSymbols.asScala.toList + Schema.TEnum( + sch.getName, + Option(sch.getNamespace), + sch.getAliases.asScala.toList, + Option(sch.getDoc), + symbols + ) + case AvroType.UNION => + val types = sch.getTypes.asScala.toList + Schema.TUnion( + NonEmptyList.fromListUnsafe(types) + ) + case AvroType.FIXED => + Schema.TFixed( + sch.getName, + Option(sch.getNamespace), + sch.getAliases.asScala.toList, + sch.getFixedSize + ) + } + } + + def order2Order(avroO: AvroSchema.Field.Order): Order = avroO match { + case AvroSchema.Field.Order.ASCENDING => Order.Ascending + case AvroSchema.Field.Order.DESCENDING => Order.Descending + case AvroSchema.Field.Order.IGNORE => Order.Ignore + } + + def field2Field(avroF: AvroSchema.Field): Field[AvroSchema] = Field( + avroF.name, + avroF.aliases.asScala.toList, + Option(avroF.doc), + Option(order2Order(avroF.order)), + avroF.schema + ) + } diff --git a/src/main/scala/avro/util.scala b/src/main/scala/avro/util.scala deleted file mode 100644 index 6f236ce7..00000000 --- a/src/main/scala/avro/util.scala +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright 2018 47 Degrees, LLC. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package skeuomorph -package avro - -import org.apache.avro.{Schema => AvroSchema, Protocol => AvroProtocol} -import org.apache.avro.Schema.{Type => AvroType} - -import scala.collection.JavaConverters._ - -import cats.data.NonEmptyList -import qq.droste._ - -object util { - - import Schema._ - - def order2Order(avroO: AvroSchema.Field.Order): Order = avroO match { - case AvroSchema.Field.Order.ASCENDING => Order.Ascending - case AvroSchema.Field.Order.DESCENDING => Order.Descending - case AvroSchema.Field.Order.IGNORE => Order.Ignore - } - - def field2Field(avroF: AvroSchema.Field): Field[AvroSchema] = Field( - avroF.name, - avroF.aliases.asScala.toList, - Option(avroF.doc), - Option(order2Order(avroF.order)), - avroF.schema - ) - - def fromProto[T](proto: AvroProtocol)(implicit T: Embed[Schema, T]): Protocol[T] = { - val toFreestyle: AvroSchema => T = scheme.ana(fromAvro) - - Protocol( - proto.getName, - Option(proto.getNamespace), - proto.getTypes.asScala.toList.map(toFreestyle), - proto.getMessages.asScala - .map({ - case (_, message) => - Message[T](message.getName, toFreestyle(message.getRequest), toFreestyle(message.getResponse)) - }) - .toList - ) - } - - /** - * Convert [[org.apache.avro.Schema]] to [[skeuomorph.avro.AvroSchema]] - */ - def fromAvro: Coalgebra[Schema, AvroSchema] = Coalgebra { sch => - sch.getType match { - case AvroType.STRING => Schema.TString() - case AvroType.BOOLEAN => Schema.TBoolean() - case AvroType.BYTES => Schema.TBytes() - case AvroType.DOUBLE => Schema.TDouble() - case AvroType.FLOAT => Schema.TFloat() - case AvroType.INT => Schema.TInt() - case AvroType.LONG => Schema.TLong() - case AvroType.NULL => Schema.TNull() - case AvroType.MAP => Schema.TMap(sch.getValueType) - case AvroType.ARRAY => Schema.TArray(sch.getElementType) - case AvroType.RECORD => - Schema.TRecord( - sch.getName, - Option(sch.getNamespace), - sch.getAliases.asScala.toList, - Option(sch.getDoc), - sch.getFields.asScala.toList.map(field2Field) - ) - case AvroType.ENUM => - val symbols = sch.getEnumSymbols.asScala.toList - Schema.TEnum( - sch.getName, - Option(sch.getNamespace), - sch.getAliases.asScala.toList, - Option(sch.getDoc), - symbols - ) - case AvroType.UNION => - val types = sch.getTypes.asScala.toList - Schema.TUnion( - NonEmptyList.fromListUnsafe(types) - ) - case AvroType.FIXED => - Schema.TFixed( - sch.getName, - Option(sch.getNamespace), - sch.getAliases.asScala.toList, - sch.getFixedSize - ) - } - } -} diff --git a/src/main/scala/freestyle/Service.scala b/src/main/scala/freestyle/Service.scala index ade2ac06..8ef03766 100644 --- a/src/main/scala/freestyle/Service.scala +++ b/src/main/scala/freestyle/Service.scala @@ -34,7 +34,7 @@ object Service { * create a [[skeuomorph.freestyle.Service]] from a [[skeuomorph.avro.Protocol]] */ def fromAvroProtocol[T, U]( - proto: avro.Schema.Protocol[T])(implicit T: Basis[avro.Schema, T], U: Basis[Schema, U]): Service[U] = { + proto: avro.Protocol[T])(implicit T: Basis[avro.Schema, T], U: Basis[Schema, U]): Service[U] = { val toFreestyle: T => U = scheme.cata(transformAvro[U].algebra) From 984fbfba5ed540e6d1005b645fb81c23d6eee99d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pepe=20Garc=C3=ADa?= Date: Wed, 1 Aug 2018 10:01:27 +0200 Subject: [PATCH 10/13] move proto.util.render to Schema --- src/main/scala/protobuf/schema.scala | 58 ++++++++++++++++++++ src/main/scala/protobuf/util.scala | 82 ---------------------------- 2 files changed, 58 insertions(+), 82 deletions(-) delete mode 100644 src/main/scala/protobuf/util.scala diff --git a/src/main/scala/protobuf/schema.scala b/src/main/scala/protobuf/schema.scala index 5dad9a5c..dbcf3cf1 100644 --- a/src/main/scala/protobuf/schema.scala +++ b/src/main/scala/protobuf/schema.scala @@ -18,6 +18,7 @@ package skeuomorph package protobuf import cats.Functor +import qq.droste.Algebra sealed trait Schema[A] @@ -79,4 +80,61 @@ object Schema { } } + + def printOption(o: Option): String = s"${o.name} = ${o.value}" + + def render: Algebra[Schema, String] = Algebra { + case TDouble() => "double" + case TFloat() => "float" + case TInt32() => "int32" + case TInt64() => "int64" + case TUint32() => "uint32" + case TUint64() => "uint64" + case TSint32() => "sint32" + case TSint64() => "sint64" + case TFixed32() => "fixed32" + case TFixed64() => "fixed64" + case TSfixed32() => "sfixed32" + case TSfixed64() => "sfixed64" + case TBool() => "bool" + case TString() => "string" + case TBytes() => "bytes" + case TNamedType(name) => name + + case TRequired(value) => s"required $value" + case TOptional(value) => s"optional $value" + case TRepeated(value) => s"repeated $value" + + case TEnum(name, symbols, options, aliases) => + val printOptions = options.map(o => s"option ${o.name} = ${o.value}").mkString("\n") + val printSymbols = symbols.map({ case (s, i) => s"$s = $i;" }).mkString("\n") + val printAliases = aliases.map({ case (s, i) => s"$s = $i;" }).mkString("\n") + + s""" +enum $name { + $printOptions + $printSymbols + $printAliases +} +""" + case TMessage(name, fields, reserved) => + val printReserved = reserved.map(l => s"reserved " + l.mkString(", ")).mkString("\n ") + def printOptions(options: List[Option]) = + if (options.isEmpty) { + "" + } else { + options.map(printOption).mkString(" [", ", ", "]") + } + + val printFields = + fields + .map(f => s"""${f.tpe} ${f.name} = ${f.position}${printOptions(f.options)};""") + .mkString("\n ") + s""" +message $name { + $printReserved + $printFields +} +""" + } } diff --git a/src/main/scala/protobuf/util.scala b/src/main/scala/protobuf/util.scala deleted file mode 100644 index 0d52f0b3..00000000 --- a/src/main/scala/protobuf/util.scala +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2018 47 Degrees, LLC. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package skeuomorph -package protobuf - -import qq.droste._ - -object util { - - import Schema._ - - def printOption(o: Option): String = s"${o.name} = ${o.value}" - - def render: Algebra[Schema, String] = Algebra { - case TDouble() => "double" - case TFloat() => "float" - case TInt32() => "int32" - case TInt64() => "int64" - case TUint32() => "uint32" - case TUint64() => "uint64" - case TSint32() => "sint32" - case TSint64() => "sint64" - case TFixed32() => "fixed32" - case TFixed64() => "fixed64" - case TSfixed32() => "sfixed32" - case TSfixed64() => "sfixed64" - case TBool() => "bool" - case TString() => "string" - case TBytes() => "bytes" - case TNamedType(name) => name - - case TRequired(value) => s"required $value" - case TOptional(value) => s"optional $value" - case TRepeated(value) => s"repeated $value" - - case TEnum(name, symbols, options, aliases) => - val printOptions = options.map(o => s"option ${o.name} = ${o.value}").mkString("\n") - val printSymbols = symbols.map({ case (s, i) => s"$s = $i;" }).mkString("\n") - val printAliases = aliases.map({ case (s, i) => s"$s = $i;" }).mkString("\n") - - s""" -enum $name { - $printOptions - $printSymbols - $printAliases -} -""" - case TMessage(name, fields, reserved) => - val printReserved = reserved.map(l => s"reserved " + l.mkString(", ")).mkString("\n ") - def printOptions(options: List[Option]) = - if (options.isEmpty) { - "" - } else { - options.map(printOption).mkString(" [", ", ", "]") - } - - val printFields = - fields - .map(f => s"""${f.tpe} ${f.name} = ${f.position}${printOptions(f.options)};""") - .mkString("\n ") - s""" -message $name { - $printReserved - $printFields -} -""" - } -} From e5a66a3cba2e528638acd3323d0791f8ab44d923 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pepe=20Garc=C3=ADa?= Date: Wed, 1 Aug 2018 10:02:35 +0200 Subject: [PATCH 11/13] make tests pass after refactor --- src/test/scala/avro/AvroSpec.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/scala/avro/AvroSpec.scala b/src/test/scala/avro/AvroSpec.scala index 4485fde9..9c45dcdd 100644 --- a/src/test/scala/avro/AvroSpec.scala +++ b/src/test/scala/avro/AvroSpec.scala @@ -38,7 +38,7 @@ class AvroSpec extends Specification with ScalaCheck { """ def convertSchema = Prop.forAll { (schema: AvroSchema) => - val test = scheme.hylo(checkSchema(schema).run, util.fromAvro.run) + val test = scheme.hylo(checkSchema(schema).run, Schema.fromAvro.run) test(schema) } From 42653c657d7f62b730aa31225abbf145357bcbf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pepe=20Garc=C3=ADa?= Date: Wed, 1 Aug 2018 10:03:50 +0200 Subject: [PATCH 12/13] make docs compile after refactor --- README.md | 2 +- docs/src/main/tut/optimizations.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 58d5eb96..c6a2e522 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ import org.apache.avro._ import skeuomorph._ import skeuomorph.freestyle.Transform.transformAvro import skeuomorph.freestyle.Schema.render -import skeuomorph.avro.util.fromAvro +import skeuomorph.avro.Schema.fromAvro import qq.droste._ import qq.droste.data._ import qq.droste.implicits._ diff --git a/docs/src/main/tut/optimizations.md b/docs/src/main/tut/optimizations.md index c04b44bb..dfc940ee 100644 --- a/docs/src/main/tut/optimizations.md +++ b/docs/src/main/tut/optimizations.md @@ -43,7 +43,7 @@ case class Product(field1: String, field2: OtherField) We solve this by substituting nested product types by it's name when they're inside a product themselves. And we do this with the -`namedTypes` combinator (in `skeuomorph.freestyle.util`): +`namedTypes` combinator (in `skeuomorph.freestyle.Optimize`): ```scala def nestedNamedTypesTrans[T](implicit T: Basis[Schema, T]): Trans[Schema, Schema, T] = Trans { From ad035d2f734f615451429ae4cd5ea1112a2ffeb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pepe=20Garc=C3=ADa?= Date: Thu, 2 Aug 2018 09:53:34 +0200 Subject: [PATCH 13/13] address comments in the PR --- README.md | 6 ++--- docs/src/main/tut/optimizations.md | 9 ++++--- src/main/scala/avro/Protocol.scala | 13 ++++------ src/main/scala/freestyle/Optimize.scala | 5 ++-- src/main/scala/freestyle/Service.scala | 15 ++++++------ src/main/scala/freestyle/schema.scala | 14 +++++------ src/main/scala/protobuf/schema.scala | 32 ++++++++++++------------- 7 files changed, 43 insertions(+), 51 deletions(-) diff --git a/README.md b/README.md index c6a2e522..f6d7696f 100644 --- a/README.md +++ b/README.md @@ -86,9 +86,9 @@ val definition = """ val schema: Schema = new Schema.Parser().parse(definition) -val parseAvroSchema: Schema => Fix[freestyle.Schema] = - scheme.hylo(transformAvro[Fix[freestyle.Schema]].algebra.run, fromAvro.run) -val printSchema: Fix[freestyle.Schema] => String = +val parseAvroSchema: Schema => Mu[freestyle.Schema] = + scheme.hylo(transformAvro[Mu[freestyle.Schema]].algebra.run, fromAvro.run) +val printSchema: Mu[freestyle.Schema] => String = scheme.cata(render) (parseAvroSchema >>> printSchema)(schema) diff --git a/docs/src/main/tut/optimizations.md b/docs/src/main/tut/optimizations.md index dfc940ee..7130e581 100644 --- a/docs/src/main/tut/optimizations.md +++ b/docs/src/main/tut/optimizations.md @@ -48,11 +48,10 @@ they're inside a product themselves. And we do this with the ```scala def nestedNamedTypesTrans[T](implicit T: Basis[Schema, T]): Trans[Schema, Schema, T] = Trans { case TProduct(name, fields) => + def nameTypes(f: Field[T]): Field[T] = f.copy(tpe = namedTypes(T)(f.tpe)) TProduct[T]( name, - fields.map { f: Field[T] => - f.copy(tpe = namedTypes(T)(f.tpe)) - } + fields.map(nameTypes) ) case other => other } @@ -71,11 +70,11 @@ def nestedNamedTypes[T: Basis[Schema, ?]]: T => T = scheme.cata(nestedNamedTypes and then apply the `namedTypes` combinator to the AST: ```tut:invisible -def ast = Fix(TNull[Fix[Schema]]()) +def ast = Mu(TNull[Mu[Schema]]()) ``` ```tut -val optimization = Optimize.namedTypes[Fix[Schema]] +val optimization = Optimize.namedTypes[Mu[Schema]] optimization(ast) ``` diff --git a/src/main/scala/avro/Protocol.scala b/src/main/scala/avro/Protocol.scala index 19e80813..5e05d8e6 100644 --- a/src/main/scala/avro/Protocol.scala +++ b/src/main/scala/avro/Protocol.scala @@ -35,18 +35,15 @@ object Protocol { case class Message[A](name: String, request: A, response: A) def fromProto[T](proto: AvroProtocol)(implicit T: Embed[Schema, T]): Protocol[T] = { - val toFreestyle: AvroSchema => T = scheme.ana(fromAvro) + val toAvroSchema: AvroSchema => T = scheme.ana(fromAvro) + def toMessage(kv: (String, AvroProtocol#Message)): Message[T] = + Message[T](kv._2.getName, toAvroSchema(kv._2.getRequest), toAvroSchema(kv._2.getResponse)) Protocol( proto.getName, Option(proto.getNamespace), - proto.getTypes.asScala.toList.map(toFreestyle), - proto.getMessages.asScala - .map({ - case (_, message) => - Message[T](message.getName, toFreestyle(message.getRequest), toFreestyle(message.getResponse)) - }) - .toList + proto.getTypes.asScala.toList.map(toAvroSchema), + proto.getMessages.asScala.toList.map(toMessage) ) } diff --git a/src/main/scala/freestyle/Optimize.scala b/src/main/scala/freestyle/Optimize.scala index 8fd62285..5c204f7a 100644 --- a/src/main/scala/freestyle/Optimize.scala +++ b/src/main/scala/freestyle/Optimize.scala @@ -44,11 +44,10 @@ object Optimize { */ def nestedNamedTypesTrans[T](implicit T: Basis[Schema, T]): Trans[Schema, Schema, T] = Trans { case TProduct(name, fields) => + def nameTypes(f: Field[T]): Field[T] = f.copy(tpe = namedTypes(T)(f.tpe)) TProduct[T]( name, - fields.map { f: Field[T] => - f.copy(tpe = namedTypes(T)(f.tpe)) - } + fields.map(nameTypes) ) case other => other } diff --git a/src/main/scala/freestyle/Service.scala b/src/main/scala/freestyle/Service.scala index 8ef03766..ce34502f 100644 --- a/src/main/scala/freestyle/Service.scala +++ b/src/main/scala/freestyle/Service.scala @@ -37,18 +37,19 @@ object Service { proto: avro.Protocol[T])(implicit T: Basis[avro.Schema, T], U: Basis[Schema, U]): Service[U] = { val toFreestyle: T => U = scheme.cata(transformAvro[U].algebra) + val toOperation: avro.Protocol.Message[T] => Operation[U] = + msg => + Service.Operation( + msg.name, + toFreestyle(msg.request), + toFreestyle(msg.response) + ) Service( proto.namespace.fold("")(identity), proto.name, proto.types.map(toFreestyle), - proto.messages.map( - msg => - Service.Operation( - msg.name, - toFreestyle(msg.request), - toFreestyle(msg.response) - )) + proto.messages.map(toOperation) ) } diff --git a/src/main/scala/freestyle/schema.scala b/src/main/scala/freestyle/schema.scala index 015a2299..1624d3ee 100644 --- a/src/main/scala/freestyle/schema.scala +++ b/src/main/scala/freestyle/schema.scala @@ -82,16 +82,14 @@ object Schema { case TSum(name, fields) => val printFields = fields.map(f => s"case object $f extends $name").mkString("\n ") s""" -sealed trait $name -object $name { - $printFields -} -""" + |sealed trait $name + |object $name { + | $printFields + |} + """.stripMargin case TProduct(name, fields) => val printFields = fields.map(f => s"${f.name}: ${f.tpe}").mkString(", ") - s""" -@message case class $name($printFields) -""" + s"@message case class $name($printFields)" } } diff --git a/src/main/scala/protobuf/schema.scala b/src/main/scala/protobuf/schema.scala index dbcf3cf1..daff7239 100644 --- a/src/main/scala/protobuf/schema.scala +++ b/src/main/scala/protobuf/schema.scala @@ -109,32 +109,30 @@ object Schema { val printOptions = options.map(o => s"option ${o.name} = ${o.value}").mkString("\n") val printSymbols = symbols.map({ case (s, i) => s"$s = $i;" }).mkString("\n") val printAliases = aliases.map({ case (s, i) => s"$s = $i;" }).mkString("\n") - s""" -enum $name { - $printOptions - $printSymbols - $printAliases -} -""" + |enum $name { + | $printOptions + | $printSymbols + | $printAliases + |} + """.stripMargin case TMessage(name, fields, reserved) => val printReserved = reserved.map(l => s"reserved " + l.mkString(", ")).mkString("\n ") def printOptions(options: List[Option]) = - if (options.isEmpty) { + if (options.isEmpty) "" - } else { - options.map(printOption).mkString(" [", ", ", "]") - } + else + options.map(printOption).mkString(start = " [", sep = ", ", end = "]") val printFields = fields - .map(f => s"""${f.tpe} ${f.name} = ${f.position}${printOptions(f.options)};""") + .map(f => s"${f.tpe} ${f.name} = ${f.position}${printOptions(f.options)};") .mkString("\n ") s""" -message $name { - $printReserved - $printFields -} -""" + |message $name { + | $printReserved + | $printFields + |} + """.stripMargin } }