Last active
September 18, 2019 06:45
-
-
Save matfournier/33dea2b3ed347b253f3737ce3cc50738 to your computer and use it in GitHub Desktop.
traversing all the things examples
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package mf.examples | |
import scala.concurrent.Future | |
import scala.concurrent.ExecutionContext | |
import scala.util.Try | |
import cats._ | |
import cats.data._ | |
import cats.implicits._ | |
case class Request(value: String) | |
case class RawResponse(value: String) | |
case class ParsedResponse(value: Int) | |
case object ParsedResponse { | |
def fromString(s: String): Either[ServiceError, ParsedResponse] = | |
Try(ParsedResponse(s.toInt)).toEither.leftMap(_ => ServiceError("parsing error")) | |
} | |
case class BatchRequest(requests: List[Request]) | |
case class BatchResponse(responses: List[RawResponse]) | |
case class ServiceError(message: String) | |
object Http { | |
def requestBatch(requests: List[Request], batchSize: Int = 2)(implicit ec: ExecutionContext): Future[Either[ServiceError, List[ParsedResponse]]] = { | |
val batches = requests.grouped(batchSize).map(batch => BatchRequest(batch)).toList | |
// this is another alternative and will fail if either the network fails OR the ANY parsing fails | |
val batchResponseE = Future.sequence(batches.map(batch => { | |
for { | |
response <- doRequest(batch) | |
} yield response.flatMap(br => parseAndCollectBatchResponse(br)) | |
})).map(_.combineAll) | |
// this is another alternative if network failure means fail the entire thing | |
// but parsing failure is ok | |
val batchResponseParseFailureOk = Future.sequence(batches.map(batch => { | |
for { | |
response <- doRequest(batch) | |
} yield response.map(br => oParseAndCollectBatchResponse(br)) | |
})).map(_.combineAll) | |
// what if this was EitherT, and ANY failure meant failure | |
val batchesTAnyFailure = batches | |
.traverse(br => { | |
val inner = EitherT(doRequest(br)).flatMap(br => EitherT.fromEither[Future](parseAndCollectBatchResponse(br))) | |
inner | |
}).map(_.combineAll) | |
// what is this was EitherT and parsing failures were ok but not network failures | |
val batchesOT = batches | |
.traverse(br => | |
EitherT(doRequest(br)).map(br => oParseAndCollectBatchResponse(br)) | |
).map(_.combineAll) | |
// tricky variation: fail on first network failure but accumulate _all parsing errors_ | |
val batchResponsesNel = Future.sequence(batches.map(batch => { | |
for { | |
response <- doRequest(batch) | |
} yield response.toValidatedNel.map(parseAndCollectBatchResponseAllErrors).combineAll | |
}) | |
).map(_.combineAll) | |
// tricky variation: accumulate _all_ failures | |
// can make this legible in many ways, this was just a lazy first cut w/o changing anything else. | |
val batchResponsesNelFailFuture = { | |
val networkFailuresCombined = Future | |
.sequence(batches.map(batch => | |
doRequest(batch).map(_.toValidatedNel).map(_.map(parseAndCollectBatchResponseAllErrors(_))) | |
) | |
) | |
networkFailuresCombined.map(_.map(_.combineAll)) | |
} | |
// and as an eitherT: trick question, there is no monad transformer for Validation. | |
batchesTAnyFailure.value | |
} | |
// if one things fails to parse error w/ it's error, otherwise give all the responses | |
private def parseAndCollectBatchResponse(br: BatchResponse): Either[ServiceError, List[ParsedResponse]] = | |
br.responses.traverse(r => ParsedResponse.fromString(r.value)) | |
private def parseAndCollectBatchResponseAllErrors(br: BatchResponse): ValidatedNel[ServiceError, List[ParsedResponse]] = | |
br.responses.traverse(r => ParsedResponse.fromString(r.value).toValidatedNel) | |
// ignore any parsing failures | |
private def oParseAndCollectBatchResponse(br: BatchResponse): List[ParsedResponse] = | |
br.responses.flatMap(r => ParsedResponse.fromString(r.value).toOption) | |
private def doRequest(request: BatchRequest): Future[Either[ServiceError, BatchResponse]] = { | |
if (request.requests.length > 5) Future.failed(new Exception("http error")) | |
else { | |
val rawResponses = request.requests.map(r => RawResponse(r.value)) | |
Future.successful(BatchResponse(rawResponses).asRight[ServiceError]) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment