Skip to content

Commit

Permalink
Add new AWS parsers/shows
Browse files Browse the repository at this point in the history
- add new parsers and shows for AWS providers
- change parsers to functors and provide instances
- provide contravariant instances for shows
- add tests for new instances
  • Loading branch information
janstenpickle committed May 9, 2018
1 parent 7b289fd commit 26f3c83
Show file tree
Hide file tree
Showing 20 changed files with 312 additions and 69 deletions.
40 changes: 23 additions & 17 deletions aws/src/main/scala/extruder/aws/AwsCredentialsInstances.scala
Expand Up @@ -2,47 +2,53 @@ package extruder.aws

import cats.Monad
import cats.data.{OptionT, ValidatedNel}
import cats.syntax.either._
import com.amazonaws.auth.{AWSCredentials, BasicAWSCredentials}
import com.amazonaws.auth.{AWSCredentials, AWSCredentialsProvider, BasicAWSCredentials}
import eu.timepit.refined._
import eu.timepit.refined.api.Refined
import eu.timepit.refined.boolean.And
import eu.timepit.refined.collection.Size
import eu.timepit.refined.numeric.Interval
import eu.timepit.refined.string.MatchesRegex
import extruder.core.{MultiParser, MultiShow, Parser}
import extruder.refined.RefinedInstances
import extruder.refined._

trait AwsCredentialsInstances extends RefinedInstances {
trait AwsCredentialsInstances {
import AwsCredentialsInstances._

implicit def credentialsParser(
implicit idParser: Parser[AwsId],
secretParser: Parser[AwsSecret]
): MultiParser[AWSCredentials] =
new MultiParser[AWSCredentials] {
override def parse[F[_]: Monad](
lookup: List[String] => OptionT[F, String]
): OptionT[F, ValidatedNel[String, AWSCredentials]] =
implicit def credentialsParser[F[_]: Monad]: MultiParser[F, AWSCredentials] =
new MultiParser[F, AWSCredentials] {
private val idParser: Parser[AwsId] = Parser[AwsId]
private val secretParser: Parser[AwsSecret] = Parser[AwsSecret]

override def parse(lookup: List[String] => OptionT[F, String]): OptionT[F, ValidatedNel[String, AWSCredentials]] =
for {
id <- lookup(List(AwsId))
secret <- lookup(List(AwsSecret))
} yield
idParser
.parse(id)
.toValidatedNel[String]
.parseNel(id)
.product(
secretParser
.parse(secret)
.toValidatedNel[String]
.parseNel(secret)
)
.map { case (i, s) => new BasicAWSCredentials(i.value, s.value) }
}

implicit def credentialsShow: MultiShow[AWSCredentials] = new MultiShow[AWSCredentials] {
implicit def credentialsProviderParser[F[_]: Monad]: MultiParser[F, AWSCredentialsProvider] =
credentialsParser.map(
credentials =>
new AWSCredentialsProvider {
override def getCredentials: AWSCredentials = credentials
override def refresh(): Unit = ()
}
)

implicit val credentialsShow: MultiShow[AWSCredentials] = new MultiShow[AWSCredentials] {
override def show(v: AWSCredentials): Map[List[String], String] =
Map(List(AwsId) -> v.getAWSAccessKeyId, List(AwsSecret) -> v.getAWSSecretKey)
}

implicit val credentialsProviderShow: MultiShow[AWSCredentialsProvider] = MultiShow.by(_.getCredentials)
}

object AwsCredentialsInstances {
Expand Down
17 changes: 14 additions & 3 deletions aws/src/main/scala/extruder/aws/AwsRegionInstances.scala
@@ -1,13 +1,24 @@
package extruder.aws

import com.amazonaws.regions.{Region, Regions}
import com.amazonaws.regions.{AwsRegionProvider, Region, Regions}
import extruder.core.{Parser, Show}

trait AwsRegionInstances {
implicit def awsRegionParser: Parser[Region] =
implicit val awsRegionParser: Parser[Region] =
Parser.catchNonFatal(region => Region.getRegion(Regions.fromName(region)))

implicit def awsRegionShow: Show[Region] = Show { r: Region =>
implicit val awsRegionProviderParser: Parser[AwsRegionProvider] = awsRegionParser.map(
region =>
new AwsRegionProvider {
override def getRegion: String = region.getName
}
)

implicit val awsRegionShow: Show[Region] = Show { r: Region =>
r.getName
}

implicit val awsRegionProviderShow: Show[AwsRegionProvider] = Show { rp: AwsRegionProvider =>
rp.getRegion
}
}
44 changes: 27 additions & 17 deletions aws/src/test/scala/extruder/aws/AwsCredentialsInstancesSpec.scala
Expand Up @@ -2,7 +2,7 @@ package extruder.aws

import cats.Id
import cats.data.{NonEmptyList, OptionT}
import com.amazonaws.auth.{AWSCredentials, BasicAWSCredentials}
import com.amazonaws.auth.{AWSCredentials, AWSCredentialsProvider, BasicAWSCredentials}
import eu.timepit.refined.api.Refined
import eu.timepit.refined.boolean.Not
import eu.timepit.refined.scalacheck.any._
Expand All @@ -28,34 +28,44 @@ class AwsCredentialsInstancesSpec extends Specification with ScalaCheck with Eit
Shows credentials $shows
"""

def passes: MatchResult[Either[NonEmptyList[String], AWSCredentials]] =
MultiParser[AWSCredentials]
.parse[Id](path => OptionT.fromOption(validData.get(path)))
def passes =
testPasses[AWSCredentials](creds => creds.getAWSAccessKeyId == validId && creds.getAWSSecretKey == validSecret) &&
testPasses[AWSCredentialsProvider](
creds =>
creds.getCredentials.getAWSAccessKeyId == validId && creds.getCredentials.getAWSSecretKey == validSecret
)

def testPasses[A](
test: A => Boolean
)(implicit parser: MultiParser[Id, A]): MatchResult[Either[NonEmptyList[String], A]] =
parser
.parse(path => OptionT.fromOption(validData.get(path)))
.value
.get
.toEither must beRight.which { creds =>
creds.getAWSAccessKeyId === validId && creds.getAWSSecretKey === validSecret
}
.toEither must beRight.which(test)

def isNoneNoData =
MultiParser[AWSCredentials]
.parse[Id](_ => OptionT.none)
MultiParser[Id, AWSCredentials]
.parse(_ => OptionT.none)
.value must beNone

def isNoneIdData =
MultiParser[AWSCredentials]
.parse[Id](path => OptionT.fromOption(Map(List(AwsId) -> validId).get(path)))
MultiParser[Id, AWSCredentials]
.parse(path => OptionT.fromOption(Map(List(AwsId) -> validId).get(path)))
.value must beNone

def isNoneSecretData =
MultiParser[AWSCredentials]
.parse[Id](path => OptionT.fromOption(Map(List(AwsSecret) -> validSecret).get(path)))
MultiParser[Id, AWSCredentials]
.parse(path => OptionT.fromOption(Map(List(AwsSecret) -> validSecret).get(path)))
.value must beNone

def fails: Prop = prop { (id: String, secret: String Refined Not[SizeIs[_40]]) =>
val data = Map(List(AwsId) -> id, List(AwsSecret) -> secret.value)
MultiParser[AWSCredentials].parse[Id](path => OptionT.fromOption(data.get(path))).value.get.toEither must beLeft
.which(_.size == 2)
def fails = testFails[AWSCredentials] && testFails[AWSCredentialsProvider]

def testFails[A](implicit parser: MultiParser[Id, A]): Prop = prop {
(id: String, secret: String Refined Not[SizeIs[_40]]) =>
val data = Map(List(AwsId) -> id, List(AwsSecret) -> secret.value)
parser.parse(path => OptionT.fromOption(data.get(path))).value.get.toEither must beLeft
.which(_.size == 2)
}

def shows: Prop = prop { (id: String, sec: String) =>
Expand Down
14 changes: 10 additions & 4 deletions aws/src/test/scala/extruder/aws/AwsRegionInstancesSpec.scala
@@ -1,6 +1,6 @@
package extruder.aws

import com.amazonaws.regions.{Region, Regions}
import com.amazonaws.regions.{AwsRegionProvider, Region, Regions}
import org.specs2.{ScalaCheck, Specification}
import extruder.aws.region._
import extruder.core.{Parser, Show}
Expand All @@ -18,15 +18,21 @@ class AwsRegionInstancesSpec extends Specification with ScalaCheck {
Shows a region $shows
"""

def passes: Prop = prop { region: Region =>
def passes = passesTest[Region] && passesTest[AwsRegionProvider]

def passesTest[A: Parser]: Prop = prop { region: Region =>
Parser[Region].parse(region.getName) must beRight(region)
}

def fails: Prop = prop { str: String =>
def fails = failsTest[Region] && passesTest[AwsRegionProvider]

def failsTest[A: Parser]: Prop = prop { str: String =>
Parser[Region].parse(str) must beLeft(s"Cannot create enum from $str value!")
}

def shows: Prop = prop { region: Region =>
def shows = showsTest[Region] && showsTest[AwsRegionProvider]

def showsTest[A: Show]: Prop = prop { region: Region =>
Show[Region].show(region) === region.getName
}
}
Expand Down
4 changes: 2 additions & 2 deletions core/src/main/scala/extruder/core/DerivedDecoders.scala
Expand Up @@ -40,7 +40,7 @@ trait DerivedDecoders { self: Decoders with DecodeTypes =>
lp: LowPriority,
refute: Refute[DecoderRefute[T]],
refuteParser: Refute[Parser[T]],
refuteMultiParser: Refute[MultiParser[T]],
refuteMultiParser: Refute[MultiParser[F, T]],
neOpt: T <:!< Option[_],
neCol: T <:!< TraversableOnce[_]
): Dec[F, T] =
Expand Down Expand Up @@ -88,7 +88,7 @@ trait DerivedDecoders { self: Decoders with DecodeTypes =>
lp: LowPriority,
refute: Refute[DecRefute[T]],
refuteParser: Refute[Parser[T]],
refuteMultiParser: Refute[MultiParser[T]]
refuteMultiParser: Refute[MultiParser[F, T]]
): Dec[F, T] =
mkDecoder { (path, _, data) =>
val newPath =
Expand Down
32 changes: 22 additions & 10 deletions core/src/main/scala/extruder/core/PrimitiveDecoders.scala
Expand Up @@ -2,14 +2,15 @@ package extruder.core

import java.net.URL

import cats.{Monad, Traverse}
import cats.{Functor, Traverse}
import cats.data.{NonEmptyList, OptionT, ValidatedNel}
import cats.instances.either._
import cats.instances.list._
import cats.instances.option._
import cats.syntax.either._
import cats.syntax.flatMap._
import cats.syntax.functor._
import extruder.instances.{MultiParserInstances, ParserInstances}
import mouse.string._
import shapeless.syntax.typeable._
import shapeless.{Lazy, LowPriority, Refute, Typeable}
Expand All @@ -30,14 +31,14 @@ trait PrimitiveDecoders { self: Decoders with DecodeTypes =>
mkDecoder[F, T]((path, default, data) => resolveValue(formatParserError(parser, path)).apply(path, default, data))

implicit def optionalMultiParserDecoder[F[_], T](
implicit parser: MultiParser[T],
implicit parser: MultiParser[F, T],
hints: Hint,
F: Eff[F]
): Dec[F, Option[T]] =
mkDecoder[F, Option[T]]((path, _, data) => multiParse(parser, path, data))

implicit def multiParserDecoder[F[_], T](
implicit parser: MultiParser[T],
implicit parser: MultiParser[F, T],
hints: Hint,
F: Eff[F],
lp: LowPriority
Expand All @@ -50,7 +51,7 @@ trait PrimitiveDecoders { self: Decoders with DecodeTypes =>
} yield result
)

private def multiParse[F[_], T](parser: MultiParser[T], path: List[String], data: DecodeData)(
private def multiParse[F[_], T](parser: MultiParser[F, T], path: List[String], data: DecodeData)(
implicit F: Eff[F],
hints: Hint
): F[Option[T]] =
Expand Down Expand Up @@ -92,7 +93,7 @@ trait PrimitiveDecoders { self: Decoders with DecodeTypes =>
implicit decoder: Lazy[Dec[F, T]],
hints: Hint,
F: Eff[F],
refute: Refute[MultiParser[T]]
refute: Refute[MultiParser[F, T]]
): Dec[F, Option[T]] =
mkDecoder[F, Option[T]] { (path, _, data) =>
val decoded = decoder.value.read(path, None, data)
Expand Down Expand Up @@ -143,12 +144,21 @@ trait PrimitiveDecoders { self: Decoders with DecodeTypes =>

}

trait MultiParser[T] extends {
def parse[F[_]: Monad](lookup: List[String] => OptionT[F, String]): OptionT[F, ValidatedNel[String, T]]
trait MultiParser[F[_], T] {
def parse(lookup: List[String] => OptionT[F, String]): OptionT[F, ValidatedNel[String, T]]
def map[A](f: T => A)(implicit F: Functor[F]): MultiParser[F, A] =
MultiParser.parser[F, A]((parse _).andThen(_.map(_.map(f))))
}

object MultiParser {
def apply[T](implicit multiParser: MultiParser[T]): MultiParser[T] = multiParser
object MultiParser extends MultiParserInstances {
def apply[F[_], T](implicit multiParser: MultiParser[F, T]): MultiParser[F, T] = multiParser
def parser[F[_], T](
f: (List[String] => OptionT[F, String]) => OptionT[F, ValidatedNel[String, T]]
): MultiParser[F, T] =
new MultiParser[F, T] {
override def parse(lookup: List[String] => OptionT[F, String]): OptionT[F, ValidatedNel[String, T]] =
f(lookup)
}
}

trait Parsers {
Expand Down Expand Up @@ -204,9 +214,11 @@ trait Parsers {

case class Parser[T](parse: String => Either[String, T]) {
def parseNel: String => ValidatedNel[String, T] = parse.andThen(_.toValidatedNel)
def map[A](f: T => A): Parser[A] = Parser[A](parse.andThen(_.map(f)))
def flatMapResult[A](f: T => Either[String, A]): Parser[A] = Parser(parse.andThen(_.flatMap(f)))
}

object Parser extends Parsers {
object Parser extends Parsers with ParserInstances {
def apply[T](implicit parser: Parser[T]): Parser[T] = parser

def fromEitherException[T](parse: String => Either[Throwable, T]): Parser[T] =
Expand Down
11 changes: 8 additions & 3 deletions core/src/main/scala/extruder/core/PrimitiveEncoders.scala
Expand Up @@ -5,7 +5,8 @@ import java.net.URL
import cats.data.NonEmptyList
import cats.instances.all._
import cats.syntax.functor._
import cats.{Traverse, Show => CatsShow}
import cats.{Contravariant, Traverse, Show => CatsShow}
import extruder.instances.{MultiShowInstances, ShowInstances}
import shapeless._

import scala.concurrent.duration.Duration
Expand Down Expand Up @@ -44,8 +45,10 @@ trait MultiShow[T] {
def show(v: T): Map[List[String], String]
}

object MultiShow {
object MultiShow extends MultiShowInstances {
def apply[T](implicit multiShow: MultiShow[T]): MultiShow[T] = multiShow
def by[T, V](f: V => T)(implicit ev: MultiShow[T]): MultiShow[V] =
Contravariant[MultiShow].contramap(ev)(f)
}

trait Shows {
Expand Down Expand Up @@ -77,7 +80,9 @@ trait Shows {

case class Show[T](show: T => String)

object Show extends Shows {
object Show extends Shows with ShowInstances {
def apply[T](implicit showWrapper: Show[T]): Show[T] = showWrapper
def apply[T](show: CatsShow[T]): Show[T] = new Show(show.show)
def by[T, V](f: V => T)(implicit ev: Show[T]): Show[V] =
Contravariant[Show].contramap(ev)(f)
}
2 changes: 1 addition & 1 deletion core/src/main/scala/extruder/core/StringMapDecoders.scala
Expand Up @@ -17,7 +17,7 @@ trait StringMapDecoders { self: Decoders with DecodeTypes =>
implicit F: Eff[F],
decoder: Dec[F, T],
hints: Hint,
refute: Refute[MultiParser[T]]
refute: Refute[MultiParser[F, T]]
): Dec[F, Map[String, T]] =
mkDecoder(
(path, default, data) =>
Expand Down
3 changes: 3 additions & 0 deletions core/src/main/scala/extruder/instances/AllInstances.scala
@@ -0,0 +1,3 @@
package extruder.instances

trait AllInstances extends MultiParserInstances with MultiShowInstances with ParserInstances with ShowInstances
11 changes: 11 additions & 0 deletions core/src/main/scala/extruder/instances/MultiParserInstances.scala
@@ -0,0 +1,11 @@
package extruder.instances

import cats.Functor
import extruder.core.MultiParser

trait MultiParserInstances {
implicit def extruderStdInstancesForMultiParser[F[_]: Functor]: Functor[MultiParser[F, ?]] =
new Functor[MultiParser[F, ?]] {
override def map[A, B](fa: MultiParser[F, A])(f: A => B): MultiParser[F, B] = fa.map(f)
}
}
12 changes: 12 additions & 0 deletions core/src/main/scala/extruder/instances/MultiShowInstances.scala
@@ -0,0 +1,12 @@
package extruder.instances

import cats.Contravariant
import extruder.core.MultiShow

trait MultiShowInstances {
implicit val extruderStdInstancesForMultiShow: Contravariant[MultiShow] = new Contravariant[MultiShow] {
override def contramap[A, B](fa: MultiShow[A])(f: B => A): MultiShow[B] = new MultiShow[B] {
override def show(b: B): Map[List[String], String] = fa.show(f(b))
}
}
}
10 changes: 10 additions & 0 deletions core/src/main/scala/extruder/instances/ParserInstances.scala
@@ -0,0 +1,10 @@
package extruder.instances

import cats.Functor
import extruder.core.Parser

trait ParserInstances {
implicit val extruderStdInstancesForParser: Functor[Parser] = new Functor[Parser] {
override def map[A, B](fa: Parser[A])(f: A => B): Parser[B] = fa.map(f)
}
}

0 comments on commit 26f3c83

Please sign in to comment.