Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion project/Build.scala
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ object Courier extends Build with OverridablePublishSettings {
// In order to keep it under control we primarily concern ourselves with these two below Scala
// version numbers:

lazy val sbtScalaVersion = "2.10.5" // the version of Scala used by the current sbt version.
lazy val sbtScalaVersion = "2.11.6" // the version of Scala used by the current sbt version.
lazy val currentScalaVersion = "2.11.6" // The current scala version.

// Our plugin runs as part of SBT so must use the same Scala version that SBT currently uses.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace org.coursera.records.test

@backend="simple"
record WithPrimitivesSimple {
intField: int
longField: long
floatField: float
doubleField: double
booleanField: boolean
stringField: string
bytesField: bytes
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package org.coursera.courier.generator

import org.coursera.records.test.WithPrimitives
import org.coursera.records.test.WithPrimitivesSimple
import org.junit.Test
import org.scalatest.junit.AssertionsForJUnit
import org.scalatest.junit.JUnitSuite
import play.api.libs.json.Json

class SimpleBackendTest extends JUnitSuite with AssertionsForJUnit {

@Test
def schemasMatch(): Unit = {
// Courier uses `string` instead of `String` so we homogenize here
val old = Json.parse(
WithPrimitives.SCHEMA.toString
.replaceAll("\"string\"", "\"String\"")
.replaceAll("\"bytes\"", "\"ByteString\"")
.replaceAll("\"boolean\"", "\"Boolean\"")
.replaceAll("\"int\"", "\"Int\"")
.replaceAll("\"long\"", "\"Long\"")
.replaceAll("\"float\"", "\"Float\"")
.replaceAll("\"double\"", "\"Double\"")
)
val `new` = WithPrimitivesSimple.SCHEMA
// We skip `name` because those are necessarily different
assert(`new` \ "type" === old \ "type")
assert(`new` \ "namespace" === old \ "namespace")
assert(`new` \ "fields" === old \ "fields")
}

}

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions scala/runtime/build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ name := "courier-runtime"
runtimeVersionSettings

libraryDependencies ++= Seq(
"com.typesafe.play" %% "play-json" % "2.6.2",
"com.chuusai" %% "shapeless" % "2.3.2",
ExternalDependencies.Pegasus.data,
ExternalDependencies.Coursera.courscala,
ExternalDependencies.JUnit.junit,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package org.coursera.courier.schema

import shapeless.DepFn0
import shapeless.HList
import shapeless.HNil
import shapeless.labelled.FieldType

import scala.reflect.ClassTag

trait FieldTypes[L <: HList] extends DepFn0 { type Out <: HList }

object FieldTypes {
type Aux[L <: HList, Out0 <: HList] = FieldTypes[L] { type Out = Out0 }

def apply[L <: HList](implicit fieldTypes: FieldTypes[L]): Aux[L, fieldTypes.Out] = fieldTypes

implicit def hnilFieldTypes[L <: HNil]: Aux[L, HNil] = new FieldTypes[L] {
type Out = HNil
def apply(): Out = HNil
}
type ::[+A, +B <: shapeless.HList] = shapeless.::[A, B]

implicit def hlistFieldTypes[K, V, Rest <: HList](
implicit fieldTypesRest: FieldTypes[Rest],
clazz: ClassTag[V]
): Aux[FieldType[K, V] :: Rest, String :: fieldTypesRest.Out] = new FieldTypes[FieldType[K, V] :: Rest] {
type Out = String :: fieldTypesRest.Out

def apply(): Out = clazz.runtimeClass.getName :: fieldTypesRest()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package org.coursera.courier.schema

import play.api.libs.json.JsArray
import play.api.libs.json.JsObject
import play.api.libs.json.Json
import shapeless.HList
import shapeless.LabelledGeneric
import shapeless.Poly1
import shapeless.Typeable
import shapeless.ops.hlist.LiftAll
import shapeless.ops.hlist.Mapper
import shapeless.ops.hlist.ToTraversable
import shapeless.ops.record.UnzipFields

import scala.reflect.ClassTag

class Schema[A] {
def asJson[
Repr <: HList,
Keys <: HList,
Values <: HList,
Typeables <: HList,
MapperOut <: HList
](implicit
clazz: ClassTag[A],
generic: LabelledGeneric.Aux[A, Repr],
unzip: UnzipFields.Aux[Repr, Keys, Values],
typeable: LiftAll.Aux[Typeable, Values, Typeables],
fieldTypes: Mapper.Aux[Schema.describe.type, Typeables, MapperOut],
traversable1: ToTraversable.Aux[Keys, List, Symbol],
traversable2: ToTraversable.Aux[MapperOut, List, String]
): JsObject = {
val keys = unzip.keys.toList.map(_.name)
val types: List[String] = fieldTypes(typeable.instances).toList
val fields = keys.zip(types).map {
case (name, typ) =>
Json.obj(
"name" -> name,
"type" -> typ
)
}
Json.obj(
"type" -> "record",
"name" -> clazz.runtimeClass.getSimpleName,
"namespace" -> clazz.runtimeClass.getPackage.getName,
"fields" -> JsArray(fields)
)
}
}

object Schema {
def apply[A] = new Schema[A]

object describe extends Poly1 {
implicit def typeable[T] = at[Typeable[T]](_.describe)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
package org.coursera.records.test

import javax.annotation.Generated

import com.linkedin.data.DataMap
import com.linkedin.data.schema.DataSchema
import com.linkedin.data.schema.UnionDataSchema
import com.linkedin.data.schema.TyperefDataSchema
import com.linkedin.data.template.Custom
import com.linkedin.data.template.DataTemplate
import com.linkedin.data.template.RecordTemplate
import com.linkedin.data.template.RequiredFieldNotPresentException
import com.linkedin.data.template.TemplateOutputCastException
import com.linkedin.data.template.UnionTemplate
import org.coursera.courier.templates.DataTemplates
import org.coursera.courier.templates.DataTemplates.DataConversion
import org.coursera.courier.templates.ScalaRecordTemplate
import org.coursera.courier.templates.ScalaArrayTemplate
import org.coursera.courier.templates.ScalaUnionTemplate
import org.coursera.courier.templates.ScalaEnumTemplate
import org.coursera.courier.templates.ScalaEnumTemplateSymbol
import org.coursera.courier.companions.UnionCompanion
import org.coursera.courier.companions.UnionMemberCompanion
import org.coursera.courier.companions.UnionWithTyperefCompanion
import org.coursera.courier.companions.RecordCompanion
import org.coursera.courier.companions.ArrayCompanion
import org.coursera.courier.companions.MapCompanion
import scala.runtime.ScalaRunTime
import com.linkedin.data.template.DataTemplateUtil
import com.linkedin.data.schema.RecordDataSchema
import com.linkedin.data.schema.UnionDataSchema
import com.linkedin.data.schema.TyperefDataSchema
import com.linkedin.data.schema.DataSchemaConstants
import com.linkedin.data.ByteString
import com.linkedin.data.DataList
import scala.collection.JavaConverters._
import scala.collection.immutable.Iterable
import scala.collection.immutable.MapLike
import scala.collection.mutable.Builder
import scala.collection.generic.CanBuildFrom
import com.linkedin.data.schema.MapDataSchema
import com.linkedin.data.schema.ArrayDataSchema
import scala.collection.GenTraversable
import org.coursera.courier.codecs.InlineStringCodec
import org.coursera.courier.coercers.SingleElementCaseClassCoercer
import scala.language.implicitConversions

@Generated(value = Array("WithPrimitives"), comments = "Courier Data Template.")
final class WithPrimitives private (private val dataMap: DataMap)
extends ScalaRecordTemplate(dataMap, WithPrimitives.SCHEMA) with Product {
WithPrimitives // force static initialization
import WithPrimitives._

lazy val intField: Int = obtainDirect(WithPrimitives.Fields.intField, classOf[java.lang.Integer])

lazy val longField: Long = obtainDirect(WithPrimitives.Fields.longField, classOf[java.lang.Long])

lazy val floatField: Float = obtainDirect(WithPrimitives.Fields.floatField, classOf[java.lang.Float])

lazy val doubleField: Double = obtainDirect(WithPrimitives.Fields.doubleField, classOf[java.lang.Double])

lazy val booleanField: Boolean = obtainDirect(WithPrimitives.Fields.booleanField, classOf[java.lang.Boolean])

lazy val stringField: String = obtainDirect(WithPrimitives.Fields.stringField, classOf[java.lang.String])

lazy val bytesField: ByteString = obtainDirect(WithPrimitives.Fields.bytesField, classOf[com.linkedin.data.ByteString])

private def setFields(intField: Int, longField: Long, floatField: Float, doubleField: Double, booleanField: Boolean, stringField: String, bytesField: ByteString): Unit = {
putDirect(WithPrimitives.Fields.intField, classOf[java.lang.Integer], Int.box(intField))
putDirect(WithPrimitives.Fields.longField, classOf[java.lang.Long], Long.box(longField))
putDirect(WithPrimitives.Fields.floatField, classOf[java.lang.Float], Float.box(floatField))
putDirect(WithPrimitives.Fields.doubleField, classOf[java.lang.Double], Double.box(doubleField))
putDirect(WithPrimitives.Fields.booleanField, classOf[java.lang.Boolean], Boolean.box(booleanField))
putDirect(WithPrimitives.Fields.stringField, classOf[java.lang.String], stringField)
putDirect(WithPrimitives.Fields.bytesField, classOf[com.linkedin.data.ByteString], bytesField)
}

override val productArity: Int = 7

override def productElement(n: Int): Any =
n match {
case 0 => intField
case 1 => longField
case 2 => floatField
case 3 => doubleField
case 4 => booleanField
case 5 => stringField
case 6 => bytesField
case _ => throw new IndexOutOfBoundsException(n.toString)
}

override val productPrefix: String = "WithPrimitives"

override def canEqual(that: Any): Boolean = that.isInstanceOf[WithPrimitives]

override def hashCode: Int = ScalaRunTime._hashCode(this)

override def equals(that: Any): Boolean = canEqual(that) && ScalaRunTime._equals(this, that)

override def toString: String = ScalaRunTime._toString(this)

override def copy(): WithPrimitives = this

def copy(intField: Int = this.intField, longField: Long = this.longField, floatField: Float = this.floatField, doubleField: Double = this.doubleField, booleanField: Boolean = this.booleanField, stringField: String = this.stringField, bytesField: ByteString = this.bytesField): WithPrimitives = {
val $dataMap = new DataMap
val $result = new WithPrimitives($dataMap)
$result.setFields(intField, longField, floatField, doubleField, booleanField, stringField, bytesField)
$dataMap.makeReadOnly()
$result
}

override def copy(dataMap: DataMap, conversion: DataConversion): WithPrimitives = {
new WithPrimitives(DataTemplates.makeImmutable(dataMap, conversion))
}

override def clone(): WithPrimitives = this
}

object WithPrimitives extends RecordCompanion[WithPrimitives] {
val SCHEMA = DataTemplateUtil.parseSchema("""{"type":"record","name":"WithPrimitives","namespace":"org.coursera.records.test","fields":[{"name":"intField","type":"int"},{"name":"longField","type":"long"},{"name":"floatField","type":"float"},{"name":"doubleField","type":"double"},{"name":"booleanField","type":"boolean"},{"name":"stringField","type":"string"},{"name":"bytesField","type":"bytes"}]}""").asInstanceOf[RecordDataSchema]

implicit val withPrimitivesCompanion: RecordCompanion[WithPrimitives] = this

private object Fields {
val intField = WithPrimitives.SCHEMA.getField("intField")
val longField = WithPrimitives.SCHEMA.getField("longField")
val floatField = WithPrimitives.SCHEMA.getField("floatField")
val doubleField = WithPrimitives.SCHEMA.getField("doubleField")
val booleanField = WithPrimitives.SCHEMA.getField("booleanField")
val stringField = WithPrimitives.SCHEMA.getField("stringField")
val bytesField = WithPrimitives.SCHEMA.getField("bytesField")
}

def apply(intField: Int, longField: Long, floatField: Float, doubleField: Double, booleanField: Boolean, stringField: String, bytesField: ByteString): WithPrimitives = {
val $dataMap = new DataMap
val $result = new WithPrimitives($dataMap)
$result.setFields(intField, longField, floatField, doubleField, booleanField, stringField, bytesField)
$dataMap.makeReadOnly()
$result
}

override def build(dataMap: DataMap, conversion: DataConversion): WithPrimitives = {
new WithPrimitives(DataTemplates.makeImmutable(dataMap, conversion))
}

def unapply(record: WithPrimitives): Option[(Int, Long, Float, Double, Boolean, String, ByteString)] = {
try {
Some((record.intField, record.longField, record.floatField, record.doubleField, record.booleanField, record.stringField, record.bytesField))
} catch {
case cast: TemplateOutputCastException => None
case notPresent: RequiredFieldNotPresentException => None
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package org.coursera.records.test

import com.linkedin.data.ByteString
import org.coursera.courier.schema.Schema

case class WithPrimitivesSimple (

intField: Int,

longField: Long,

floatField: Float,

doubleField: Double,

booleanField: Boolean,

stringField: String,

bytesField: ByteString

)
object WithPrimitivesSimple {
val SCHEMA = Schema[WithPrimitivesSimple].asJson
}