Skip to content

Commit

Permalink
Xor -> Either
Browse files Browse the repository at this point in the history
With `Either` becoming left-biased in Scala 2.12, cats is moving away from `Xor`. This is a very literal replacement to try and stay ahead of that change.
  • Loading branch information
Philip Wills committed Oct 25, 2016
1 parent b1989b7 commit 2c14e25
Show file tree
Hide file tree
Showing 13 changed files with 131 additions and 135 deletions.
22 changes: 11 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,11 @@ scala> case class Farmer(name: String, age: Long, farm: Farm)

scala> val putResult = Scanamo.put(client)("farmer")(Farmer("McDonald", 156L, Farm(List("sheep", "cow"))))
scala> Scanamo.get[Farmer](client)("farmer")('name -> "McDonald")
res1: Option[cats.data.Xor[error.DynamoReadError, Farmer]] = Some(Right(Farmer(McDonald,156,Farm(List(sheep, cow)))))
res1: Option[Either[error.DynamoReadError, Farmer]] = Some(Right(Farmer(McDonald,156,Farm(List(sheep, cow)))))
```

The `Xor` represents the possibility that an item might exist, but not be parseable into the given
type, in this case `Farmer`. For more information on `Xor`, see the
[Cats documentation](http://typelevel.org/cats/tut/xor.html).
The `Either` represents the possibility that an item might exist, but not be parsable into the given
type, in this case `Farmer`.

Like all the examples in this README and the Scaladoc, this creates a table, so that it
can be checked using [sbt-doctest](https://github.com/tkawachi/sbt-doctest), but the same
Expand All @@ -64,6 +63,7 @@ abstraction to reduce noise when defining multiple operations against the same t
```scala
scala> import com.gu.scanamo._
scala> import com.gu.scanamo.syntax._
scala> import cats.syntax.either._

scala> val client = LocalDynamoDB.client()
scala> import com.amazonaws.services.dynamodbv2.model.ScalarAttributeType._
Expand All @@ -86,7 +86,7 @@ scala> val operations = for {
| } yield results

scala> Scanamo.exec(client)(operations)
res1: Set[cats.data.Xor[error.DynamoReadError, LuckyWinner]] = Set(Right(LuckyWinner(Charlie,human)), Right(LuckyWinner(Violet,blueberry)))
res1: Set[Either[error.DynamoReadError, LuckyWinner]] = Set(Right(LuckyWinner(Charlie,human)), Right(LuckyWinner(Violet,blueberry)))
```

Note that when using `Table` no operations are actually executed against DynamoDB until `exec` is called.
Expand Down Expand Up @@ -114,7 +114,7 @@ scala> val operations = for {
| } yield tubesStartingWithC.toList

scala> Scanamo.exec(client)(operations)
res1: List[cats.data.Xor[error.DynamoReadError, Transport]] = List(Right(Transport(Underground,Central)), Right(Transport(Underground,Circle)))
res1: List[Either[error.DynamoReadError, Transport]] = List(Right(Transport(Underground,Central)), Right(Transport(Underground,Circle)))
```

### Updating
Expand All @@ -137,8 +137,8 @@ scala> val operations = for {
| } yield updated

scala> Scanamo.exec(client)(operations)
res1: cats.data.Xor[error.DynamoReadError, Team] = Right(Team(Watford,2,List(Blissett, Barnes),None))
```
res1: Either[error.DynamoReadError, Team] = Right(Team(Watford,2,List(Blissett, Barnes),None))
```

### Using Indexes

Expand Down Expand Up @@ -167,7 +167,7 @@ scala> LocalDynamoDB.withTableWithSecondaryIndex(client)("transport", "colour-in
| } yield maroonLine.toList
| Scanamo.exec(client)(operations)
| }
res0: List[cats.data.Xor[error.DynamoReadError, Transport]] = List(Right(Transport(Underground,Metropolitan,Maroon)))
res0: List[Either[error.DynamoReadError, Transport]] = List(Right(Transport(Underground,Metropolitan,Maroon)))
```

### Non-blocking requests
Expand Down Expand Up @@ -198,7 +198,7 @@ scala> val ops = for {
| } yield bunce

scala> concurrent.Await.result(ScanamoAsync.exec(client)(ops), 5.seconds)
res1: Option[cats.data.Xor[error.DynamoReadError, Farmer]] = Some(Right(Farmer(Bunce,52,Farm(List(goose)))))
res1: Option[Either[error.DynamoReadError, Farmer]] = Some(Right(Farmer(Bunce,52,Farm(List(goose)))))
```

### Custom Formats
Expand Down Expand Up @@ -233,7 +233,7 @@ scala> val operations = for {
| } yield results

scala> Scanamo.exec(client)(operations).toList
res1: List[cats.data.Xor[error.DynamoReadError, Foo]] = List(Right(Foo(1970-01-01T00:00:00.000Z)))
res1: List[Either[error.DynamoReadError, Foo]] = List(Right(Foo(1970-01-01T00:00:00.000Z)))
```


Expand Down
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ resolvers += Resolver.sonatypeRepo("snapshots")
libraryDependencies ++= Seq(
"com.amazonaws" % "aws-java-sdk-dynamodb" % "1.11.8",
"com.chuusai" %% "shapeless" % "2.3.2",
"org.typelevel" %% "cats-free" % "0.7.0",
"org.typelevel" %% "cats-free" % "0.7.2",

"com.github.mpilquist" %% "simulacrum" % "0.8.0",

Expand Down
11 changes: 6 additions & 5 deletions src/main/scala/com/gu/scanamo/DerivedDynamoFormat.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.gu.scanamo

import cats.data.{NonEmptyList, Validated, Xor}
import cats.data.{NonEmptyList, Validated}
import cats.syntax.either._
import com.amazonaws.services.dynamodbv2.model.AttributeValue
import com.gu.scanamo.error.{DynamoReadError, InvalidPropertiesError, MissingProperty, PropertyReadError}
import macrocompat.bundle
Expand Down Expand Up @@ -32,11 +33,11 @@ trait DerivedDynamoFormat extends NoFormatError {
def read(av: AttributeValue): Validated[InvalidPropertiesError, FieldType[K, V] :: T] = {
val fieldName = fieldWitness.value.name

val possibleValue = av.getM.asScala.get(fieldName).map(headFormat.value.read).orElse(headFormat.value.default.map(Xor.right))
val possibleValue = av.getM.asScala.get(fieldName).map(headFormat.value.read).orElse(headFormat.value.default.map(Either.right))

val validatedValue = possibleValue.getOrElse(Xor.left[DynamoReadError, V](MissingProperty))
val validatedValue = possibleValue.getOrElse(Either.left[DynamoReadError, V](MissingProperty))

def withPropertyError(x: Xor[DynamoReadError, V]): Validated[InvalidPropertiesError, V] =
def withPropertyError(x: Either[DynamoReadError, V]): Validated[InvalidPropertiesError, V] =
x.leftMap(e => InvalidPropertiesError(NonEmptyList(PropertyReadError(fieldName, e), Nil))).toValidated

val head: Validated[InvalidPropertiesError, FieldType[K, V]] = withPropertyError(validatedValue).map(field[K](_))
Expand All @@ -52,7 +53,7 @@ trait DerivedDynamoFormat extends NoFormatError {

implicit def generic[T, R](implicit gen: LabelledGeneric.Aux[T, R], formatR: Lazy[ConstructedDynamoFormat[R]]): DynamoFormat[T] =
new DynamoFormat[T] {
def read(av: AttributeValue): Xor[DynamoReadError, T] = formatR.value.read(av).map(gen.from).toXor
def read(av: AttributeValue): Either[DynamoReadError, T] = formatR.value.read(av).map(gen.from).toEither
def write(t: T): AttributeValue = formatR.value.write(gen.to(t))
}
}
Expand Down
Loading

0 comments on commit 2c14e25

Please sign in to comment.