diff --git a/core/src/main/scala/mapper/package.scala b/core/src/main/scala/mapper/package.scala deleted file mode 100644 index ceae4f4..0000000 --- a/core/src/main/scala/mapper/package.scala +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2017 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 freestyle.cassandra - -import java.nio.ByteBuffer - -import cats.MonadError -import freestyle.cassandra.codecs.ByteBufferCodec -import shapeless._ -import shapeless.labelled.FieldType - -package object mapper { - - abstract class FieldMapper(val name: String) { - def serialize[M[_]](implicit E: MonadError[M, Throwable]): M[ByteBuffer] - } - - trait FieldListMapper[A] { - def map(a: A): List[FieldMapper] - } - - trait FieldMapperPrimitive { - - def createFieldMapper[A](f: A => List[FieldMapper]): FieldListMapper[A] = - new FieldListMapper[A] { - override def map(a: A): List[FieldMapper] = f(a) - } - - implicit def primitiveFieldMapper[K <: Symbol, H, T <: HList]( - implicit witness: Witness.Aux[K], - codec: Lazy[ByteBufferCodec[H]], - tMapper: FieldListMapper[T]): FieldListMapper[FieldType[K, H] :: T] = { - val fieldName = witness.value.name - createFieldMapper { hlist => - val fieldMapper = new FieldMapper(fieldName) { - override def serialize[M[_]](implicit E: MonadError[M, Throwable]): M[ByteBuffer] = - codec.value.serialize(hlist.head) - } - fieldMapper :: tMapper.map(hlist.tail) - } - } - } - - trait FieldMapperGeneric extends FieldMapperPrimitive { - - implicit def genericMapper[A, R]( - implicit gen: LabelledGeneric.Aux[A, R], - mapper: Lazy[FieldListMapper[R]]): FieldListMapper[A] = - createFieldMapper(value => mapper.value.map(gen.to(value))) - - implicit val hnilMapper: FieldListMapper[HNil] = new FieldListMapper[HNil] { - override def map(a: HNil): List[FieldMapper] = Nil - } - - } - - object FieldListMapper extends FieldMapperGeneric - - object FieldMapperExpanded extends FieldMapperGeneric { - - implicit def hconsMapper[K, H, T <: HList]( - implicit hMapper: Lazy[FieldListMapper[H]], - tMapper: FieldListMapper[T]): FieldListMapper[FieldType[K, H] :: T] = - createFieldMapper { hlist => hMapper.value.map(hlist.head) ++ tMapper.map(hlist.tail) - } - } - -} diff --git a/core/src/main/scala/query/FieldLister.scala b/core/src/main/scala/query/FieldLister.scala new file mode 100644 index 0000000..1f9efdc --- /dev/null +++ b/core/src/main/scala/query/FieldLister.scala @@ -0,0 +1,57 @@ +/* + * Copyright 2017 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 freestyle.cassandra +package query + +import shapeless._ +import shapeless.labelled.FieldType + +trait FieldLister[A] { + val list: List[String] +} + +trait FieldListerPrimitive { + implicit def primitiveFieldLister[K <: Symbol, H, T <: HList]( + implicit witness: Witness.Aux[K], + tLister: FieldLister[T]): FieldLister[FieldType[K, H] :: T] = + FieldLister[FieldType[K, H] :: T](witness.value.name :: tLister.list) +} + +trait FieldListerGeneric extends FieldListerPrimitive { + + implicit def genericLister[A, R]( + implicit gen: LabelledGeneric.Aux[A, R], + lister: Lazy[FieldLister[R]]): FieldLister[A] = FieldLister[A](lister.value.list) + + implicit val hnilLister: FieldLister[HNil] = FieldLister[HNil](Nil) + +} + +object FieldLister extends FieldListerGeneric { + def apply[A](l: List[String]): FieldLister[A] = new FieldLister[A] { + override val list: List[String] = l + } +} + +object FieldListerExpanded extends FieldListerGeneric { + + implicit def hconsLister[K, H, T <: HList]( + implicit hLister: Lazy[FieldLister[H]], + tLister: FieldLister[T]): FieldLister[FieldType[K, H] :: T] = + FieldLister[FieldType[K, H] :: T](hLister.value.list ++ tLister.list) + +} diff --git a/core/src/main/scala/mapper/ByteBufferMapper.scala b/core/src/main/scala/query/mapper/ByteBufferMapper.scala similarity index 97% rename from core/src/main/scala/mapper/ByteBufferMapper.scala rename to core/src/main/scala/query/mapper/ByteBufferMapper.scala index 26178b7..d2b25b1 100644 --- a/core/src/main/scala/mapper/ByteBufferMapper.scala +++ b/core/src/main/scala/query/mapper/ByteBufferMapper.scala @@ -15,7 +15,7 @@ */ package freestyle.cassandra -package mapper +package query.mapper trait ByteBufferMapper[A] { def map(a: A): List[FieldMapper] diff --git a/core/src/main/scala/query/mapper/ByteBufferToField.scala b/core/src/main/scala/query/mapper/ByteBufferToField.scala new file mode 100644 index 0000000..726787f --- /dev/null +++ b/core/src/main/scala/query/mapper/ByteBufferToField.scala @@ -0,0 +1,66 @@ +/* + * Copyright 2017 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 freestyle.cassandra +package query.mapper + +import cats.MonadError +import freestyle.cassandra.codecs.ByteBufferCodec +import freestyle.cassandra.query._ +import shapeless._ +import shapeless.labelled.{FieldBuilder, FieldType} +import shapeless.{::, HList, Witness} + +trait FromReader[A] { + def apply[M[_]](reader: ByteBufferReader)(implicit ME: MonadError[M, Throwable]): M[A] +} + +trait GenericFromReader { + + implicit val hnilFromReader: FromReader[HNil] = new FromReader[HNil] { + override def apply[M[_]](reader: ByteBufferReader)( + implicit ME: MonadError[M, Throwable]): M[HNil] = ME.pure(HNil) + } + + implicit def hconsFromReader[K <: Symbol, V, L <: HList]( + implicit + witness: Witness.Aux[K], + codec: ByteBufferCodec[V], + grT: FromReader[L], + printer: Printer): FromReader[FieldType[K, V] :: L] = + new FromReader[FieldType[K, V] :: L] { + override def apply[M[_]](reader: ByteBufferReader)( + implicit ME: MonadError[M, Throwable]): M[FieldType[K, V] :: L] = { + val newName = printer.print(witness.value.name) + ME.flatMap(reader.read(newName)) { byteBuffer => + ME.map2(codec.deserialize(byteBuffer), grT(reader)) { + case (result, l) => new FieldBuilder[K].apply(result) :: l + } + } + } + } + + implicit def productFromReader[A, L <: HList]( + implicit + gen: LabelledGeneric.Aux[A, L], + grL: FromReader[L]): FromReader[A] = + new FromReader[A] { + override def apply[M[_]](reader: ByteBufferReader)( + implicit ME: MonadError[M, Throwable]): M[A] = ME.map(grL(reader))(gen.from) + } +} + +object GenericFromReader extends GenericFromReader \ No newline at end of file diff --git a/core/src/main/scala/query/mapper/FieldToByteBuffer.scala b/core/src/main/scala/query/mapper/FieldToByteBuffer.scala new file mode 100644 index 0000000..a4b4520 --- /dev/null +++ b/core/src/main/scala/query/mapper/FieldToByteBuffer.scala @@ -0,0 +1,75 @@ +/* + * Copyright 2017 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 freestyle.cassandra +package query.mapper + +import java.nio.ByteBuffer + +import cats.MonadError +import freestyle.cassandra.codecs.ByteBufferCodec +import shapeless._ +import shapeless.labelled.FieldType + +abstract class FieldMapper(val name: String) { + def serialize[M[_]](implicit E: MonadError[M, Throwable]): M[ByteBuffer] +} + +trait FieldListMapper[A] { + def map(a: A): List[FieldMapper] +} + +trait FieldMapperPrimitive { + + implicit def primitiveFieldMapper[K <: Symbol, H, T <: HList]( + implicit witness: Witness.Aux[K], + codec: Lazy[ByteBufferCodec[H]], + tMapper: FieldListMapper[T]): FieldListMapper[FieldType[K, H] :: T] = { + val fieldName = witness.value.name + FieldListMapper { hlist => + val fieldMapper = new FieldMapper(fieldName) { + override def serialize[M[_]](implicit E: MonadError[M, Throwable]): M[ByteBuffer] = + codec.value.serialize(hlist.head) + } + fieldMapper :: tMapper.map(hlist.tail) + } + } +} + +trait FieldMapperGeneric extends FieldMapperPrimitive { + + implicit def genericMapper[A, R]( + implicit gen: LabelledGeneric.Aux[A, R], + mapper: Lazy[FieldListMapper[R]]): FieldListMapper[A] = + FieldListMapper(value => mapper.value.map(gen.to(value))) + + implicit val hnilMapper: FieldListMapper[HNil] = FieldListMapper[HNil](_ => Nil) + +} + +object FieldListMapper extends FieldMapperGeneric { + def apply[A](f: (A) => List[FieldMapper]): FieldListMapper[A] = new FieldListMapper[A] { + override def map(a: A): List[FieldMapper] = f(a) + } +} + +object FieldMapperExpanded extends FieldMapperGeneric { + + implicit def hconsMapper[K, H, T <: HList]( + implicit hMapper: Lazy[FieldListMapper[H]], + tMapper: FieldListMapper[T]): FieldListMapper[FieldType[K, H] :: T] = + FieldListMapper(hlist => hMapper.value.map(hlist.head) ++ tMapper.map(hlist.tail)) +} diff --git a/core/src/main/scala/query/query.scala b/core/src/main/scala/query/query.scala index 2230c6b..cb6d0cf 100644 --- a/core/src/main/scala/query/query.scala +++ b/core/src/main/scala/query/query.scala @@ -16,49 +16,30 @@ package freestyle.cassandra -import shapeless._ -import shapeless.labelled.FieldType +import java.nio.ByteBuffer + +import cats.MonadError package object query { - trait FieldLister[A] { - val list: List[String] + trait ByteBufferReader { + def read[M[_]](name: String)(implicit ME: MonadError[M, Throwable]): M[ByteBuffer] } - trait FieldListerPrimitive { - implicit def primitiveFieldLister[K <: Symbol, H, T <: HList]( - implicit witness: Witness.Aux[K], - tLister: FieldLister[T]): FieldLister[FieldType[K, H] :: T] = - new FieldLister[FieldType[K, H] :: T] { - override val list: List[String] = witness.value.name :: tLister.list - } + trait Printer { + def print(name: String): String } - trait FieldListerGeneric extends FieldListerPrimitive { - - implicit def genericLister[A, R]( - implicit gen: LabelledGeneric.Aux[A, R], - lister: Lazy[FieldLister[R]]): FieldLister[A] = new FieldLister[A] { - override val list: List[String] = lister.value.list - } - - implicit val hnilLister: FieldLister[HNil] = new FieldLister[HNil] { - override val list: List[String] = Nil + object Printer { + def apply(f: String => String): Printer = new Printer { + override def print(name: String): String = f(name) } - } - object FieldLister extends FieldListerGeneric - - object FieldListerExpanded extends FieldListerGeneric { + val identityPrinter: Printer = Printer(identity) - implicit def hconsLister[K, H, T <: HList]( - implicit hLister: Lazy[FieldLister[H]], - tLister: FieldLister[T]): FieldLister[FieldType[K, H] :: T] = - new FieldLister[FieldType[K, H] :: T] { - override val list: List[String] = hLister.value.list ++ tLister.list - } + val lowerCasePrinter: Printer = Printer(_.toLowerCase) - } + val upperCasePrinter: Printer = Printer(_.toUpperCase) } diff --git a/core/src/test/scala/mapper/ByteBufferMapperSpec.scala b/core/src/test/scala/query/mapper/ByteBufferMapperSpec.scala similarity index 99% rename from core/src/test/scala/mapper/ByteBufferMapperSpec.scala rename to core/src/test/scala/query/mapper/ByteBufferMapperSpec.scala index 71577dc..05b585d 100644 --- a/core/src/test/scala/mapper/ByteBufferMapperSpec.scala +++ b/core/src/test/scala/query/mapper/ByteBufferMapperSpec.scala @@ -15,7 +15,7 @@ */ package freestyle.cassandra -package mapper +package query.mapper import java.nio.ByteBuffer diff --git a/core/src/test/scala/query/mapper/ByteBufferToFieldSpec.scala b/core/src/test/scala/query/mapper/ByteBufferToFieldSpec.scala new file mode 100644 index 0000000..459ef00 --- /dev/null +++ b/core/src/test/scala/query/mapper/ByteBufferToFieldSpec.scala @@ -0,0 +1,109 @@ +/* + * Copyright 2017 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 freestyle.cassandra +package query.mapper + +import java.nio.ByteBuffer + +import cats.MonadError +import com.datastax.driver.core.{ProtocolVersion, TypeCodec} +import freestyle.cassandra.query._ +import org.scalacheck.Prop._ +import org.scalatest.prop.Checkers +import org.scalatest.{Matchers, WordSpec} +import org.scalacheck.ScalacheckShapeless._ + +import scala.util.{Failure, Success, Try} + +class ByteBufferToFieldSpec extends WordSpec with Matchers with Checkers { + + case class User(name: String, age: Int) + + implicit val stringTypeCodec: TypeCodec[String] = TypeCodec.varchar() + implicit val protocolVersion: ProtocolVersion = ProtocolVersion.V3 + + implicit val printer: Printer = identityPrinter + + import GenericFromReader._ + val fromReader: FromReader[User] = implicitly[FromReader[User]] + + "fromReader" should { + + import cats.instances.try_._ + + "return the right value when the reader return a success response" in { + + check { + forAll { user: User => + val reader = new ByteBufferReader() { + override def read[M[_]](name: String)( + implicit ME: MonadError[M, Throwable]): M[ByteBuffer] = + name match { + case "name" => ME.pure(stringTypeCodec.serialize(user.name, protocolVersion)) + case "age" => ME.pure(TypeCodec.cint().serialize(user.age, protocolVersion)) + } + } + + fromReader[Try](reader) == Success(user) + } + } + + } + + "return the failure when the reader fails returning the ByteBuffer for the 'name' field" in { + + check { + forAll { user: User => + val exception = new RuntimeException("Test Exception") + val reader = new ByteBufferReader() { + override def read[M[_]](name: String)( + implicit ME: MonadError[M, Throwable]): M[ByteBuffer] = + name match { + case "name" => ME.raiseError(exception) + case "age" => ME.pure(TypeCodec.cint().serialize(user.age, protocolVersion)) + } + } + + fromReader[Try](reader) == Failure(exception) + } + } + + } + + "return the failure when the reader fails returning the ByteBuffer for the 'age' field" in { + + check { + forAll { user: User => + val exception = new RuntimeException("Test Exception") + val reader = new ByteBufferReader() { + override def read[M[_]](name: String)( + implicit ME: MonadError[M, Throwable]): M[ByteBuffer] = + name match { + case "name" => ME.pure(stringTypeCodec.serialize(user.name, protocolVersion)) + case "age" => ME.raiseError(exception) + } + } + + fromReader[Try](reader) == Failure(exception) + } + } + + } + + } + +}