Skeuomorph

Skeuomorph is a library for transforming different schemas in Scala. It provides schema definitions as non-recursive ADTs, and transformations & optimizations via recursion schemes.

This library is primarily intended to be used at mu, but it’s completely independent from it, so anybody can use it.

Skeuomorph depends heavily on cats and droste.

Schemas

Currently skeuomorph supports 3 different schemas:

And provides conversions between them. This means that you can get a org.apache.avro.Schema value, and convert it to protobuf, for example. Or to a mu service description.

Installation

You can install skeuomorph as follows:

libraryDependencies += "io.higherkindness" %% "skeuomorph" % "0.2.2"

Examples

Parsing an Avro schema and converting it into Scala code

Given an Avro .avpr schema:

val definition = """
{
  "namespace": "example.avro",
  "type": "record",
  "name": "User",
  "fields": [
    {
      "name": "name",
      "type": "string"
    },
    {
      "name": "favorite_number",
      "type": [
        "int",
        "null"
      ]
    },
    {
      "name": "favorite_color",
      "type": [
        "string",
        "null"
      ]
    }
  ]
}
"""

We can parse it, transform it into a Mu schema and then convert it into Scala code like this:

import org.apache.avro.{Protocol => AvroProtocol, _}
import higherkindness.skeuomorph.mu.Transform.transformAvro
import higherkindness.skeuomorph.mu.MuF
import higherkindness.skeuomorph.mu.codegen
import higherkindness.skeuomorph.avro.AvroF.fromAvro
import higherkindness.droste._
import higherkindness.droste.data._
import higherkindness.droste.data.Mu._
import cats.implicits._
import scala.meta._

val avroSchema: Schema = new Schema.Parser().parse(definition)

val toMuSchema: Schema => Mu[MuF] =
  scheme.hylo(transformAvro[Mu[MuF]].algebra, fromAvro)

val printSchemaAsScala: Mu[MuF] => Either[String, String] =
  codegen.schema(_).map(_.syntax)

(toMuSchema >>> println)(avroSchema)
println("=====")
(toMuSchema >>> printSchemaAsScala >>> println)(avroSchema)

It would generate the following output:

Mu(TProduct(User,Some(example.avro),List(Field(name,Mu(TString()),None), Field(favorite_number,Mu(TCoproduct(NonEmptyList(Mu(TSimpleInt(_32)), Mu(TNull())))),None), Field(favorite_color,Mu(TCoproduct(NonEmptyList(Mu(TString()), Mu(TNull())))),None)),List(),List()))

Right(final case class User(name: root.java.lang.String, favorite_number: root.scala.Option[root.scala.Int], favorite_color: root.scala.Option[root.java.lang.String]))

Protobuf

Parsing a proto3 .proto file and converting into Scala code

Given the proto file below:

user.proto

syntax = "proto3";

package example.proto;

message User {
    string name = 1;
    int64 favorite_number = 2;
    string favorite_color = 3;
}

We can parse it, transform it into a Mu protocol and then convert it into Scala code like this:

import cats.effect.IO
import cats.effect.unsafe.implicits.global
import higherkindness.skeuomorph.mu
import higherkindness.skeuomorph.mu.{CompressionType, MuF}
import higherkindness.skeuomorph.protobuf._
import higherkindness.droste.data.Mu
import higherkindness.droste.data.Mu._
import cats.implicits._
import scala.meta._

val source = ParseProto.ProtoSource("user.proto", new java.io.File(".").getAbsolutePath ++ "/microsite/protobuf")

val protobufProtocol: Protocol[Mu[ProtobufF]] = ParseProto.parseProto[IO, Mu[ProtobufF]].parse(source).unsafeRunSync()

val toMuProtocol: Protocol[Mu[ProtobufF]] => mu.Protocol[Mu[MuF]] = { p: Protocol[Mu[ProtobufF]] =>
  mu.Protocol.fromProtobufProto(CompressionType.Identity)(p)
}

val printProtocolAsScala: mu.Protocol[Mu[MuF]] => Either[String, String] = { p =>
  val streamCtor: (Type, Type) => Type.Apply = {
    case (f, a) => t"_root_.fs2.Stream[$f, $a]"
  }
  mu.codegen.protocol(p, streamCtor).map(_.syntax)
}

(toMuProtocol >>> println)(protobufProtocol)
println("=====")
(toMuProtocol >>> printProtocolAsScala >>> println)(protobufProtocol)

It would generate the following output:

Protocol(Some(user),Some(example.proto),List(),List(Mu(TProduct(User,None,List(Field(name,Mu(TString()),Some(List(1))), Field(favorite_number,Mu(TProtobufInt(_64,List())),Some(List(2))), Field(favorite_color,Mu(TString()),Some(List(3)))),List(),List()))),List(),List())

Right(package example.proto import root.higherkindness.mu.rpc.protocol._ object user { final case class User(@root.pbdirect.pbIndex(1) name: root.java.lang.String, @root.pbdirect.pbIndex(2) favorite_number: root.scala.Long, @root.pbdirect.pbIndex(3) favorite_color: root.java.lang.String) })

Proto2 Incompatibility

Please note that the design of Skeuomorph supports Proto3, and while it can still generate Scala code using Proto2, not all fields will be supported (most notably optional fields). For more details on this incompatibility, please see the schema notes. For this reason, we strongly encourage only using Skeuomorph with Proto3 schemas.

Skeuomorph in the wild

If you wish to add your library here please consider a PR to include it in the list below.

Name Description
mu purely functional library for building RPC endpoint based services with support for RPC and HTTP/2

Copyright

Skeuomorph is designed and developed by 47 Degrees

Copyright (C) 2018-2019 47 Degrees. http://47deg.com