From ab20bbac6337baba1d9f994200615b0b1b78b1a4 Mon Sep 17 00:00:00 2001 From: Martijn Hoekstra Date: Wed, 16 Aug 2017 20:13:08 +0200 Subject: [PATCH] I like aligned things --- .scalafmt.conf | 8 +- build.sbt | 115 ++++++------ src/main/scala/DefaultResources.scala | 5 +- src/main/scala/Runner.scala | 14 +- src/main/scala/api/Api.scala | 161 ++++++++++------- src/main/scala/api/java/ApiResults.scala | 33 +++- src/main/scala/api/java/javaAPI.scala | 51 ++++-- src/main/scala/codec/CirceSupport.scala | 5 +- src/main/scala/codec/Decoders.scala | 121 +++++++++---- src/main/scala/data/ApiResultF.scala | 78 ++++---- src/main/scala/data/Battleground.scala | 2 +- src/main/scala/data/CalendarEntryF.scala | 8 +- src/main/scala/data/CalendarMatchF.scala | 2 +- src/main/scala/data/DraftF.scala | 58 +++--- src/main/scala/data/HeroF.scala | 13 +- src/main/scala/data/LiveStream.scala | 2 +- src/main/scala/data/MatchF.scala | 153 +++++++++------- src/main/scala/data/Patch.scala | 2 +- src/main/scala/data/PickF.scala | 2 +- src/main/scala/data/PlayerF.scala | 79 +++++---- src/main/scala/data/PlayerPhoto.scala | 2 +- src/main/scala/data/Region.scala | 2 +- src/main/scala/data/Roles.scala | 3 +- src/main/scala/data/TeamF.scala | 21 ++- src/main/scala/data/TeamLogo.scala | 2 +- src/main/scala/data/TournamentF.scala | 21 ++- src/main/scala/data/TournamentStage.scala | 2 +- src/main/scala/fix/Deep.scala | 14 +- src/main/scala/fix/IdAnnotated.scala | 29 ++- src/main/scala/fix/WithId.scala | 5 +- src/main/scala/net/Auth.scala | 71 ++++---- src/main/scala/net/Bridge.scala | 7 +- src/main/scala/net/ClientRunnable.scala | 88 ++++----- src/main/scala/net/Crawler.scala | 7 +- src/main/scala/net/Endpoints.scala | 18 +- src/main/scala/net/ErrorDetailed.scala | 49 +++-- src/main/scala/net/Filters.scala | 35 ++-- src/main/scala/net/QueryBuilder.scala | 5 +- src/main/scala/net/Tokens.scala | 10 +- src/main/scala/net/UnfoldApiResult.scala | 167 ++++++++++-------- src/main/scala/net/util/QueryInstances.scala | 61 ++++--- src/main/scala/stats/Stats.scala | 147 +++++++-------- src/test/scala/HeroDecoderSpec.scala | 5 +- src/test/scala/TeamResultDecoderSpec.scala | 2 +- src/test/scala/TournamentResultDecoder.scala | 2 +- src/test/scala/codecs/ExampleSpec.scala | 17 +- src/test/scala/codecs/RoundtripSpec.scala | 32 ++-- .../scala/discipline/DisciplineSpec.scala | 20 +-- .../scala/discipline/EitherExampleSpec.scala | 26 +-- .../scala/discipline/data/Generators.scala | 134 +++++++------- .../discipline/data/HeroFInstancesSpec.scala | 34 ++-- .../net/util/QueryInstanceSpec.scala | 34 ++-- src/test/scala/net/EndpointsSpec.scala | 20 +-- src/test/scala/stats/HeroStatsSpec.scala | 58 +++--- src/test/scala/stats/StatsGenerators.scala | 1 - 55 files changed, 1163 insertions(+), 900 deletions(-) diff --git a/.scalafmt.conf b/.scalafmt.conf index 1054d2a..520d31c 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -2,7 +2,13 @@ align.tokens = [ {code="=>",owner="Case"} "%" "%%" - "%%%" + "%%%" + "=" + {code="<-",owner="For"} ] +align.arrowEnumeratorGenerator = true + +align.openParenCallSite = true + maxColumn = 120 \ No newline at end of file diff --git a/build.sbt b/build.sbt index df1a91b..f0a70b4 100644 --- a/build.sbt +++ b/build.sbt @@ -13,10 +13,10 @@ scmInfo := Some( developers := List( Developer( - id = "MartijnHoekstra", - name = "Martijn Hoekstra", + id = "MartijnHoekstra", + name = "Martijn Hoekstra", email = "Martijnhoekstra@gmail.com", - url = url("https://github.com/martijnhoekstra") + url = url("https://github.com/martijnhoekstra") ) ) @@ -27,7 +27,7 @@ publishTo := { if (isSnapshot.value) Some("snapshots" at nexus + "content/repositories/snapshots") else - Some("releases" at nexus + "service/local/staging/deploy/maven2") + Some("releases" at nexus + "service/local/staging/deploy/maven2") } organization := "com.heroestools" @@ -38,52 +38,53 @@ version := "0.0.4-SNAPSHOT" scalaVersion := "2.12.3" -scalacOptions ++= List( - "-deprecation", // Emit warning and location for usages of deprecated APIs. - "-encoding", "utf-8", // Specify character encoding used by source files. - "-explaintypes", // Explain type errors in more detail. - "-feature", // Emit warning and location for usages of features that should be imported explicitly. - "-language:existentials", // Existential types (besides wildcard types) can be written and inferred - "-language:experimental.macros", // Allow macro definition (besides implementation and application) - "-language:higherKinds", // Allow higher-kinded types - "-language:implicitConversions", // Allow definition of implicit functions called views - "-unchecked", // Enable additional warnings where generated code depends on assumptions. - "-Xcheckinit", // Wrap field accessors to throw an exception on uninitialized access. - "-Xfatal-warnings", // Fail the compilation if there are any warnings. - "-Xfuture", // Turn on future language features. - "-Xlint:adapted-args", // Warn if an argument list is modified to match the receiver. - "-Xlint:by-name-right-associative", // By-name parameter of right associative operator. - "-Xlint:constant", // Evaluation of a constant arithmetic expression results in an error. - "-Xlint:delayedinit-select", // Selecting member of DelayedInit. - "-Xlint:doc-detached", // A Scaladoc comment appears to be detached from its element. - "-Xlint:inaccessible", // Warn about inaccessible types in method signatures. - "-Xlint:infer-any", // Warn when a type argument is inferred to be `Any`. - "-Xlint:missing-interpolator", // A string literal appears to be missing an interpolator id. - "-Xlint:nullary-override", // Warn when non-nullary `def f()' overrides nullary `def f'. - "-Xlint:nullary-unit", // Warn when nullary methods return Unit. - "-Xlint:option-implicit", // Option.apply used implicit view. - "-Xlint:package-object-classes", // Class or object defined in package object. - "-Xlint:poly-implicit-overload", // Parameterized overloaded implicit methods are not visible as view bounds. - "-Xlint:private-shadow", // A private field (or class parameter) shadows a superclass field. - "-Xlint:stars-align", // Pattern sequence wildcard must align with sequence component. - "-Xlint:type-parameter-shadow", // A local type parameter shadows a type already in scope. - "-Xlint:unsound-match", // Pattern match may not be typesafe. - "-Yno-adapted-args", // Do not adapt an argument list (either by inserting () or creating a tuple) to match the receiver. - "-Ypartial-unification", // Enable partial unification in type constructor inference - "-Ywarn-dead-code", // Warn when dead code is identified. - "-Ywarn-extra-implicit", // Warn when more than one implicit parameter section is defined. - "-Ywarn-inaccessible", // Warn about inaccessible types in method signatures. - "-Ywarn-infer-any", // Warn when a type argument is inferred to be `Any`. - "-Ywarn-nullary-override", // Warn when non-nullary `def f()' overrides nullary `def f'. - "-Ywarn-nullary-unit", // Warn when nullary methods return Unit. - "-Ywarn-numeric-widen", // Warn when numerics are widened. - "-Ywarn-unused:implicits", // Warn if an implicit parameter is unused. - "-Ywarn-unused:imports", // Warn if an import selector is not referenced. - "-Ywarn-unused:locals", // Warn if a local definition is unused. - "-Ywarn-unused:params", // Warn if a value parameter is unused. - "-Ywarn-unused:patvars", // Warn if a variable bound in a pattern is unused. - "-Ywarn-unused:privates", // Warn if a private member is unused. - "-Ywarn-value-discard" // Warn when non-Unit expression results are unused. +scalacOptions ++= List( + "-deprecation", // Emit warning and location for usages of deprecated APIs. + "-encoding", + "utf-8", // Specify character encoding used by source files. + "-explaintypes", // Explain type errors in more detail. + "-feature", // Emit warning and location for usages of features that should be imported explicitly. + "-language:existentials", // Existential types (besides wildcard types) can be written and inferred + "-language:experimental.macros", // Allow macro definition (besides implementation and application) + "-language:higherKinds", // Allow higher-kinded types + "-language:implicitConversions", // Allow definition of implicit functions called views + "-unchecked", // Enable additional warnings where generated code depends on assumptions. + "-Xcheckinit", // Wrap field accessors to throw an exception on uninitialized access. + "-Xfatal-warnings", // Fail the compilation if there are any warnings. + "-Xfuture", // Turn on future language features. + "-Xlint:adapted-args", // Warn if an argument list is modified to match the receiver. + "-Xlint:by-name-right-associative", // By-name parameter of right associative operator. + "-Xlint:constant", // Evaluation of a constant arithmetic expression results in an error. + "-Xlint:delayedinit-select", // Selecting member of DelayedInit. + "-Xlint:doc-detached", // A Scaladoc comment appears to be detached from its element. + "-Xlint:inaccessible", // Warn about inaccessible types in method signatures. + "-Xlint:infer-any", // Warn when a type argument is inferred to be `Any`. + "-Xlint:missing-interpolator", // A string literal appears to be missing an interpolator id. + "-Xlint:nullary-override", // Warn when non-nullary `def f()' overrides nullary `def f'. + "-Xlint:nullary-unit", // Warn when nullary methods return Unit. + "-Xlint:option-implicit", // Option.apply used implicit view. + "-Xlint:package-object-classes", // Class or object defined in package object. + "-Xlint:poly-implicit-overload", // Parameterized overloaded implicit methods are not visible as view bounds. + "-Xlint:private-shadow", // A private field (or class parameter) shadows a superclass field. + "-Xlint:stars-align", // Pattern sequence wildcard must align with sequence component. + "-Xlint:type-parameter-shadow", // A local type parameter shadows a type already in scope. + "-Xlint:unsound-match", // Pattern match may not be typesafe. + "-Yno-adapted-args", // Do not adapt an argument list (either by inserting () or creating a tuple) to match the receiver. + "-Ypartial-unification", // Enable partial unification in type constructor inference + "-Ywarn-dead-code", // Warn when dead code is identified. + "-Ywarn-extra-implicit", // Warn when more than one implicit parameter section is defined. + "-Ywarn-inaccessible", // Warn about inaccessible types in method signatures. + "-Ywarn-infer-any", // Warn when a type argument is inferred to be `Any`. + "-Ywarn-nullary-override", // Warn when non-nullary `def f()' overrides nullary `def f'. + "-Ywarn-nullary-unit", // Warn when nullary methods return Unit. + "-Ywarn-numeric-widen", // Warn when numerics are widened. + "-Ywarn-unused:implicits", // Warn if an implicit parameter is unused. + "-Ywarn-unused:imports", // Warn if an import selector is not referenced. + "-Ywarn-unused:locals", // Warn if a local definition is unused. + "-Ywarn-unused:params", // Warn if a value parameter is unused. + "-Ywarn-unused:patvars", // Warn if a variable bound in a pattern is unused. + "-Ywarn-unused:privates", // Warn if a private member is unused. + "-Ywarn-value-discard" // Warn when non-Unit expression results are unused. ) addCompilerPlugin("org.scalamacros" % "paradise" % "2.1.0" cross CrossVersion.full) @@ -121,10 +122,10 @@ val matryoshkaDependencies = List( val simulacrumDependencies = List("com.github.mpilquist" %% "simulacrum" % "0.10.0") -libraryDependencies ++= - circeDependencies ++ - fs2httpDependencies ++ - matryoshkaDependencies ++ - catsDependencies ++ - simulacrumDependencies ++ - testDependencies +libraryDependencies ++= + circeDependencies ++ + fs2httpDependencies ++ + matryoshkaDependencies ++ + catsDependencies ++ + simulacrumDependencies ++ + testDependencies diff --git a/src/main/scala/DefaultResources.scala b/src/main/scala/DefaultResources.scala index 3f59290..ff1c364 100644 --- a/src/main/scala/DefaultResources.scala +++ b/src/main/scala/DefaultResources.scala @@ -3,7 +3,7 @@ package masterleague4s import java.nio.channels.AsynchronousChannelGroup import java.util.concurrent.Executors -import fs2.{ Scheduler, Strategy } +import fs2.{Scheduler, Strategy} object DefaultResources { @@ -11,7 +11,8 @@ object DefaultResources { implicit val S = Strategy.fromExecutor(ES) - implicit val Sch = Scheduler.fromScheduledExecutorService(Executors.newScheduledThreadPool(4, Strategy.daemonThreadFactory("S"))) + implicit val Sch = + Scheduler.fromScheduledExecutorService(Executors.newScheduledThreadPool(4, Strategy.daemonThreadFactory("S"))) implicit val AG = AsynchronousChannelGroup.withThreadPool(ES) diff --git a/src/main/scala/Runner.scala b/src/main/scala/Runner.scala index 4721080..7b388f6 100644 --- a/src/main/scala/Runner.scala +++ b/src/main/scala/Runner.scala @@ -44,14 +44,14 @@ object Runner { case Left(err) => List(s"error: $err") case Right(m) => m.toList.map { case (id, _) => s"Match #$id" } }) foreach println -*/ + */ /* println("PLAYERS:") (allPlayers[Task].unsafeAttemptRun match { case Left(err) => List(s"error: $err") case Right(m) => m.toList.map { case (id, player) => s"Player #$id is ${player.nickname}" } }) foreach println - */ + */ /* val matchlist = allMatches[Task](credentials).unsafeAttemptRun match { case Left(err) => { println(err); List() } @@ -66,7 +66,7 @@ object Runner { println("ETC in HGC Europe - Open Division") val tournystatsstats = masterleague4s.stats.Stats.forHeroInTourny(21l, 35l, matchlist) tournystatsstats.pretty.foreach(println) - */ + */ println("HEROES (Specialists):") import masterleague4s.net.filters._ @@ -91,8 +91,8 @@ object Runner { case Right(m) => m.toList.map { case (id, team) => s"Team #$id is ${team.name}" } }) foreach println - - + + println("REGIONS:") (regions.unsafeAttemptRun match { case Left(err) => List(s"error: $err") @@ -121,6 +121,6 @@ object Runner { case Right(Left(thr)) => List(s"error: $thr") case Right(Right(l)) => l.map { entry => s"an event occurs at ${entry.date.atOffset(ZoneOffset.UTC)}" } }) foreach println -*/ + */ } -} \ No newline at end of file +} diff --git a/src/main/scala/api/Api.scala b/src/main/scala/api/Api.scala index 4463171..87c4903 100644 --- a/src/main/scala/api/Api.scala +++ b/src/main/scala/api/Api.scala @@ -27,8 +27,9 @@ import authorization.Auth object Api { - def streamAPI[A](uri: Uri @@ A, delay: FiniteDuration)(implicit decoder: Decoder[A]): Stream[Task, Either[Err, UriApiResult[A]]] = { - type ErrOr[AA] = Either[Err, AA] + def streamAPI[A](uri: Uri @@ A, delay: FiniteDuration)( + implicit decoder: Decoder[A]): Stream[Task, Either[Err, UriApiResult[A]]] = { + type ErrOr[AA] = Either[Err, AA] type ErrOrApi[AA] = ErrOr[UriApiResult[AA]] val tx: Task[Stream[Task, ErrOrApi[A]]] = http.client[Task]().map { client => @@ -37,7 +38,7 @@ object Api { def uris2as(ereq: ErrOr[Uri @@ A]): Stream[Task, ErrOrApi[A]] = ereq match { case Right(uri) => getEntries(uri)(implicitly[Catchable[Task]], decoder)(client).map(_.toEither) - case Left(err) => Stream(Left(err)) + case Left(err) => Stream(Left(err)) } Crawler.crawl(Attempt.successful(uri).toEither, uris2as, results2uri, time.sleep[Task](delay)) } @@ -46,31 +47,39 @@ object Api { Stream.force(tx) } - def streamAPIResults[A: Decoder](uri: Uri @@ A, delay: FiniteDuration): Stream[Task, Either[Err, A]] = streamAPI(uri, delay).flatMap { - case Right(ar) => Stream.emits(ar.results.map(Right(_))) - case Left(err) => Stream(Left(err)) - } + def streamAPIResults[A: Decoder](uri: Uri @@ A, delay: FiniteDuration): Stream[Task, Either[Err, A]] = + streamAPI(uri, delay).flatMap { + case Right(ar) => Stream.emits(ar.results.map(Right(_))) + case Left(err) => Stream(Left(err)) + } - def apiToMap[A: WithId](uri: Uri @@ A, delay: FiniteDuration)(implicit decoder: Decoder[A]): Task[Either[Err, Map[Long, A]]] = { - val results = streamAPIResults(uri, delay) + def apiToMap[A: WithId](uri: Uri @@ A, delay: FiniteDuration)( + implicit decoder: Decoder[A]): Task[Either[Err, Map[Long, A]]] = { + val results = streamAPIResults(uri, delay) val idprovider = implicitly[WithId[A]] - results.runFold(Attempt.successful(Map.empty[Long, A])) { - case (res, next) => for { - have <- res - res <- Attempt.fromEither(next) - } yield have.updated(idprovider.id(res), res) - }.map(_.toEither) + results + .runFold(Attempt.successful(Map.empty[Long, A])) { + case (res, next) => + for { + have <- res + res <- Attempt.fromEither(next) + } yield have.updated(idprovider.id(res), res) + } + .map(_.toEither) } def apiToList[A: Decoder](uri: Uri @@ A, delay: FiniteDuration): Task[Either[Err, List[A]]] = { val results = streamAPIResults(uri, delay) - results.runFold(Attempt.successful(List.empty[A])) { - case (res, next) => for { - have <- res - res <- Attempt.fromEither(next) - } yield res :: have - }.map(_.toEither) + results + .runFold(Attempt.successful(List.empty[A])) { + case (res, next) => + for { + have <- res + res <- Attempt.fromEither(next) + } yield res :: have + } + .map(_.toEither) } def runSingleArray[A: WithId](uri: Uri @@ A)(implicit decoder: Decoder[A]): Task[Either[Err, Map[Long, A]]] = { @@ -85,21 +94,26 @@ object Api { body <- Stream.eval(response.bodyAs[List[A]]) } yield body - liststream.runFold(Attempt.successful(Map.empty[Long, A])) { - case (res, next) => for { - have <- res - batch <- next - } yield (have ++ batch.map(r => (idprovider.id(r), r)).toMap) - }.map(_.toEither) + liststream + .runFold(Attempt.successful(Map.empty[Long, A])) { + case (res, next) => + for { + have <- res + batch <- next + } yield (have ++ batch.map(r => (idprovider.id(r), r)).toMap) + } + .map(_.toEither) } - def runToMap[F[_]: Async, A: Decoder, K, V](uri: Uri @@ A, tokenprovider: Option[ClientRunnable[F, Stream[F, Token]]])(k: A => K, v: A => V): F[Map[K, V]] = { + def runToMap[F[_]: Async, A: Decoder, K, V]( + uri: Uri @@ A, + tokenprovider: Option[ClientRunnable[F, Stream[F, Token]]])(k: A => K, v: A => V): F[Map[K, V]] = { //TODO: Address this wart? def trySingle[AA](stream: Stream[F, AA]): F[AA] = runSingle(stream).map { case Single(a) => a - case Empty => throw new Exception("head of empty stream") - case Multiple => throw new Exception("multiple elements where one was expected") + case Empty => throw new Exception("head of empty stream") + case Multiple => throw new Exception("multiple elements where one was expected") } sealed trait SingleResult[+AA] @@ -108,21 +122,23 @@ object Api { case class Single[AA](a: AA) extends SingleResult[AA] object SingleResult { def one[AA](a: AA): SingleResult[AA] = Single(a) - def none[AA]: SingleResult[AA] = Empty - def multi[AA]: SingleResult[AA] = Multiple + def none[AA]: SingleResult[AA] = Empty + def multi[AA]: SingleResult[AA] = Multiple } def runSingle[AA](stream: Stream[F, AA]): F[SingleResult[AA]] = stream.runFold(SingleResult.none[AA]) { case (Empty, a) => Single(a) - case _ => Multiple + case _ => Multiple } - def gatherOption(option: Option[Token]): ClientRunnable[F, F[Map[K, V]]] = for { - x <- UnfoldApiResult.linearizeApiResult(uri, time.sleep[F](1200.milliseconds), option) - } yield x.runFold(Map.empty[K, V])((m, page) => { - //TODO: key and value projections can and should be pushed deeper into the stack - page.results.foldLeft(m) { case (mm, a) => mm.updated(k(a), v(a)) } - }) + def gatherOption(option: Option[Token]): ClientRunnable[F, F[Map[K, V]]] = + for { + x <- UnfoldApiResult.linearizeApiResult(uri, time.sleep[F](1200.milliseconds), option) + } yield + x.runFold(Map.empty[K, V])((m, page) => { + //TODO: key and value projections can and should be pushed deeper into the stack + page.results.foldLeft(m) { case (mm, a) => mm.updated(k(a), v(a)) } + }) val runnable = tokenprovider match { case None => gatherOption(None) @@ -130,13 +146,12 @@ object Api { tokenrunnable.flatMap((tokenstream: Stream[F, Token]) => { val mappedtokens: Stream[F, ClientRunnable[F, F[Map[K, V]]]] = tokenstream.map(t => gatherOption(Some(t))) - - val firstrunnable: F[ClientRunnable[F, F[Map[K, V]]]] = trySingle(mappedtokens) - - val clientliftable: HttpClient[F] => F[Map[K, V]] = (client) => for { - runnable <- firstrunnable - map <- runnable.run(client) - } yield map + val firstrunnable: F[ClientRunnable[F, F[Map[K, V]]]] = trySingle(mappedtokens) + val clientliftable: HttpClient[F] => F[Map[K, V]] = (client) => + for { + runnable <- firstrunnable + map <- runnable.run(client) + } yield map ClientRunnable.lift(clientliftable) @@ -146,7 +161,11 @@ object Api { } //oh dear - http.client[F](requestCodec = spinoco.protocol.http.codec.HttpRequestHeaderCodec.codec(authorization.TokenAuthorization.headerCodec)).flatMap(client => runnable.run(client)) + http + .client[F]( + requestCodec = + spinoco.protocol.http.codec.HttpRequestHeaderCodec.codec(authorization.TokenAuthorization.headerCodec)) + .flatMap(client => runnable.run(client)) } @@ -155,26 +174,46 @@ object Api { def authorize[F[_]: Catchable](user: String, pass: String) = Auth.getToken[F](Endpoints.auth, user, pass) - def allTournaments[F[_]](credentials: Option[(String, String)] = None)(implicit ev: Async[F]) = runToMap(Endpoints.tournaments, credentials.map { case (user, pass) => authorize(user, pass)(ev) })(t => t._1, t => t._2) + def allTournaments[F[_]](credentials: Option[(String, String)] = None)(implicit ev: Async[F]) = + runToMap(Endpoints.tournaments, credentials.map { + case (user, pass) => authorize(user, pass)(ev) + })(t => t._1, t => t._2) + + def allMatches[F[_]: Async](credentials: Option[(String, String)] = None) = + matchesWhere(MatchFilter.empty, credentials) - def allMatches[F[_]: Async](credentials: Option[(String, String)] = None) = matchesWhere(MatchFilter.empty, credentials) - def matchesWhere[F[_]](filter: MatchFilter, credentials: Option[(String, String)] = None)(implicit ev: Async[F]) = runToMap(Endpoints.matches.filter(filter), credentials.map { case (user, pass) => authorize(user, pass)(ev) })(t => t._1, t => t._2) + def matchesWhere[F[_]](filter: MatchFilter, credentials: Option[(String, String)] = None)(implicit ev: Async[F]) = + runToMap(Endpoints.matches.filter(filter), credentials.map { + case (user, pass) => authorize(user, pass)(ev) + })(t => t._1, t => t._2) def allHeroes[F[_]: Async](credentials: Option[(String, String)] = None) = heroesWhere(HeroFilter.empty, credentials) - def heroesWhere[F[_]](filter: HeroFilter, credentials: Option[(String, String)] = None)(implicit ev: Async[F]) = runToMap(Endpoints.heroes.filter(filter), credentials.map { case (user, pass) => authorize(user, pass)(ev) })(t => t._1, t => t._2) - def allPlayers[F[_]](credentials: Option[(String, String)] = None)(implicit ev: Async[F]) = runToMap(Endpoints.players, credentials.map { case (user, pass) => authorize(user, pass)(ev) })(t => t._1, t => t._2) - def allTeams[F[_]](credentials: Option[(String, String)] = None)(implicit ev: Async[F]) = runToMap(Endpoints.teams, credentials.map { case (user, pass) => authorize(user, pass)(ev) })(t => t._1, t => t._2) + def heroesWhere[F[_]](filter: HeroFilter, credentials: Option[(String, String)] = None)(implicit ev: Async[F]) = + runToMap(Endpoints.heroes.filter(filter), credentials.map { + case (user, pass) => authorize(user, pass)(ev) + })(t => t._1, t => t._2) - def matches(wait: FiniteDuration) = apiToMap[IdMatch](Endpoints.matches, wait) - def heroes(wait: FiniteDuration) = apiToMap[IdHero](Endpoints.heroes, wait) - def players(wait: FiniteDuration) = apiToMap[IdPlayer](Endpoints.players, wait) + def allPlayers[F[_]](credentials: Option[(String, String)] = None)(implicit ev: Async[F]) = + runToMap(Endpoints.players, credentials.map { + case (user, pass) => authorize(user, pass)(ev) + })(t => t._1, t => t._2) + + def allTeams[F[_]](credentials: Option[(String, String)] = None)(implicit ev: Async[F]) = + runToMap(Endpoints.teams, credentials.map { + case (user, pass) => authorize(user, pass)(ev) + })(t => t._1, t => t._2) + + def matches(wait: FiniteDuration) = apiToMap[IdMatch](Endpoints.matches, wait) + def heroes(wait: FiniteDuration) = apiToMap[IdHero](Endpoints.heroes, wait) + def players(wait: FiniteDuration) = apiToMap[IdPlayer](Endpoints.players, wait) def tournaments(wait: FiniteDuration) = apiToMap[IdTournament](Endpoints.tournaments, wait) - def calendar(wait: FiniteDuration): Task[Either[Err, List[CalendarEntryId]]] = apiToList[CalendarEntryId](Endpoints.calendar, wait) - def teams(wait: FiniteDuration) = apiToMap[IdTeam](Endpoints.teams, wait) + def teams(wait: FiniteDuration) = apiToMap[IdTeam](Endpoints.teams, wait) + + def calendar(wait: FiniteDuration) = apiToList[CalendarEntryId](Endpoints.calendar, wait) - val regions = runSingleArray[IdRegion](Endpoints.regions) - val patches = runSingleArray[IdPatch](Endpoints.patches) + val regions = runSingleArray[IdRegion](Endpoints.regions) + val patches = runSingleArray[IdPatch](Endpoints.patches) val battlegrounds = runSingleArray[IdBattleground](Endpoints.battlegrounds) -} \ No newline at end of file +} diff --git a/src/main/scala/api/java/ApiResults.scala b/src/main/scala/api/java/ApiResults.scala index 02003f8..f934418 100644 --- a/src/main/scala/api/java/ApiResults.scala +++ b/src/main/scala/api/java/ApiResults.scala @@ -6,12 +6,32 @@ import java.time.LocalDate case class Hero(id: Long, name: String, role: Long) case class Team(id: Long, name: String, region: Long) -case class Player(id: Long, team: Option[Long], region: Long, nickname: String, realname: String, country: String, role: Long) +case class Player(id: Long, + team: Option[Long], + region: Long, + nickname: String, + realname: String, + country: String, + role: Long) case class TournamentStage(id: Long, name: String) //This id == MatchEntry.stage? -case class Tournament(id: Long, name: String, start_date: LocalDate, end_date: LocalDate, region: Option[Long], stages: java.util.List[TournamentStage]) +case class Tournament(id: Long, + name: String, + start_date: LocalDate, + end_date: LocalDate, + region: Option[Long], + stages: java.util.List[TournamentStage]) case class Pick(hero: Long, player: Long) case class Draft(team: Long, is_winner: Boolean, bans: java.util.List[Long], picks: java.util.List[Pick]) -case class Match(id: Long, date: Instant, patch: Long, tournament: Long, stage: Long, round: String, series: Long, game: Int, map: Long, drafts: java.util.List[Draft]) +case class Match(id: Long, + date: Instant, + patch: Long, + tournament: Long, + stage: Long, + round: String, + series: Long, + game: Int, + map: Long, + drafts: java.util.List[Draft]) case class GameMap(id: Long, name: String) case class Region(id: Long, name: String) case class Patch(id: Long, name: String, from_date: LocalDate, to_date: LocalDate) @@ -19,5 +39,8 @@ case class Patch(id: Long, name: String, from_date: LocalDate, to_date: LocalDat //Calendar entries have no id case class LiveStream(country: String, caster: String, viewers: Int) case class CalendarMatch(datetime: Instant, name: String, format: String, left_team: Long, right_team: Long) -case class CalendarItem(date: Instant, stage: Long, is_live: Boolean, streams: java.util.List[LiveStream], matches: java.util.List[CalendarMatch]) - +case class CalendarItem(date: Instant, + stage: Long, + is_live: Boolean, + streams: java.util.List[LiveStream], + matches: java.util.List[CalendarMatch]) diff --git a/src/main/scala/api/java/javaAPI.scala b/src/main/scala/api/java/javaAPI.scala index e5bceef..2264b30 100644 --- a/src/main/scala/api/java/javaAPI.scala +++ b/src/main/scala/api/java/javaAPI.scala @@ -1,4 +1,3 @@ - package masterleague4s package javaapi @@ -11,14 +10,15 @@ import data._ import Serialized._ object Api { - import api.{ Api => PlainApi } + import api.{Api => PlainApi} private[this] val wait = 1.seconds - private[this] def doUnspeakableJavaThingsM[A, B](t: Task[Either[scodec.Err, Map[Long, A]]], projection: A => B) = t.unsafeRun.fold( - err => throw new Exception(err.message), - map => mapAsJavaMap(map.map { case (key, value) => (key, projection(value)) }) - ) + private[this] def doUnspeakableJavaThingsM[A, B](t: Task[Either[scodec.Err, Map[Long, A]]], projection: A => B) = + t.unsafeRun.fold( + err => throw new Exception(err.message), + map => mapAsJavaMap(map.map { case (key, value) => (key, projection(value)) }) + ) def asJavaPick(pick: PickF[Long, Long]) = pick match { case PickF(hero: Long, player: Long) => javaapi.Pick(hero, player) @@ -29,11 +29,13 @@ object Api { } def asJavaDraft(draft: DraftId) = draft match { - case DraftF(team: Long, is_winner: Boolean, bans, picks) => javaapi.Draft(team, is_winner, seqAsJavaList(bans), seqAsJavaList(picks.map(asJavaPick))) + case DraftF(team: Long, is_winner: Boolean, bans, picks) => + javaapi.Draft(team, is_winner, seqAsJavaList(bans), seqAsJavaList(picks.map(asJavaPick))) } def asJavaMatch(mat: IdMatch) = mat match { - case (id, MatchF(date, patch, tournament, stage, round, series, game, map, _, drafts)) => Match(id, date, patch, tournament, stage, round, series, game, map, seqAsJavaList(drafts.map(asJavaDraft))) + case (id, MatchF(date, patch, tournament, stage, round, series, game, map, _, drafts)) => + Match(id, date, patch, tournament, stage, round, series, game, map, seqAsJavaList(drafts.map(asJavaDraft))) } def asJavaHero(hero: IdHero) = hero match { @@ -41,11 +43,13 @@ object Api { } def asJavaPlayer(player: IdPlayer) = player match { - case (id, PlayerF(team, region, nickname, realname, country, role, _, _)) => Player(id, team, region, nickname, realname, country, Roles.reverseRoles(role)) + case (id, PlayerF(team, region, nickname, realname, country, role, _, _)) => + Player(id, team, region, nickname, realname, country, Roles.reverseRoles(role)) } def asJavaTournament(tourny: IdTournament) = tourny match { - case (id, TournamentF(name, start_date, end_date, region, _, stages)) => Tournament(id, name, start_date, end_date, region, seqAsJavaList(stages.map(asJavaStage))) + case (id, TournamentF(name, start_date, end_date, region, _, stages)) => + Tournament(id, name, start_date, end_date, region, seqAsJavaList(stages.map(asJavaStage))) } def asJavaTeam(team: IdTeam) = team match { @@ -69,23 +73,34 @@ object Api { } def asJavaCalMatch(m: CalendarIdMatch) = m match { - case CalendarMatchF(datetime, name, format, left_team, right_team) => javaapi.CalendarMatch(datetime, name, format, left_team, right_team) + case CalendarMatchF(datetime, name, format, left_team, right_team) => + javaapi.CalendarMatch(datetime, name, format, left_team, right_team) } def asJavaCal(calitem: CalendarEntryId) = calitem match { case CalendarEntryF(date, stage, is_live, streams, matches) => - CalendarItem(date, stage, is_live, seqAsJavaList(streams.map(asJavaStream)), seqAsJavaList(matches.map(asJavaCalMatch))) + CalendarItem(date, + stage, + is_live, + seqAsJavaList(streams.map(asJavaStream)), + seqAsJavaList(matches.map(asJavaCalMatch))) } - def getMatches(): java.util.Map[Long, Match] = doUnspeakableJavaThingsM(PlainApi.matches(wait), asJavaMatch) - def getHeroes(): java.util.Map[Long, Hero] = doUnspeakableJavaThingsM(PlainApi.heroes(wait), asJavaHero) + def getMatches(): java.util.Map[Long, Match] = doUnspeakableJavaThingsM(PlainApi.matches(wait), asJavaMatch) + def getHeroes(): java.util.Map[Long, Hero] = doUnspeakableJavaThingsM(PlainApi.heroes(wait), asJavaHero) def getPlayers(): java.util.Map[Long, Player] = doUnspeakableJavaThingsM(PlainApi.players(wait), asJavaPlayer) - def getTournaments(): java.util.Map[Long, Tournament] = doUnspeakableJavaThingsM(PlainApi.tournaments(wait), asJavaTournament) - def getCalendar(): java.util.List[CalendarItem] = PlainApi.calendar(wait).unsafeRun.fold(err => throw new Exception(err.message), list => seqAsJavaList(list.map(asJavaCal))) + def getTournaments(): java.util.Map[Long, Tournament] = + doUnspeakableJavaThingsM(PlainApi.tournaments(wait), asJavaTournament) + def getCalendar(): java.util.List[CalendarItem] = + PlainApi + .calendar(wait) + .unsafeRun + .fold(err => throw new Exception(err.message), list => seqAsJavaList(list.map(asJavaCal))) def getTeams(): java.util.Map[Long, Team] = doUnspeakableJavaThingsM(PlainApi.teams(wait), asJavaTeam) def getRegions(): java.util.Map[Long, Region] = doUnspeakableJavaThingsM(PlainApi.regions, asJavaRegion) - def getPatches(): java.util.Map[Long, Patch] = doUnspeakableJavaThingsM(PlainApi.patches, asJavaPatch) - def getBattlegrounds(): java.util.Map[Long, GameMap] = doUnspeakableJavaThingsM(PlainApi.battlegrounds, asJavaGameMap) + def getPatches(): java.util.Map[Long, Patch] = doUnspeakableJavaThingsM(PlainApi.patches, asJavaPatch) + def getBattlegrounds(): java.util.Map[Long, GameMap] = + doUnspeakableJavaThingsM(PlainApi.battlegrounds, asJavaGameMap) } diff --git a/src/main/scala/codec/CirceSupport.scala b/src/main/scala/codec/CirceSupport.scala index 6d64e13..566f666 100644 --- a/src/main/scala/codec/CirceSupport.scala +++ b/src/main/scala/codec/CirceSupport.scala @@ -3,7 +3,7 @@ package codec object CirceSupport { import io.circe._ - import scodec.{ Attempt, Err } + import scodec.{Attempt, Err} import spinoco.protocol.http.header.value.HttpCharset import spinoco.fs2.http.body.BodyDecoder import io.circe.parser._ @@ -18,7 +18,8 @@ object CirceSupport { json <- parse(s) a <- json.as[A] } yield a - Attempt.fromEither(x.left.map(ex => Err(s"Failed to decode string $es ContentType: $ct, charset: $chs, err: ${ex.getMessage}"))) + Attempt.fromEither(x.left.map(ex => + Err(s"Failed to decode string $es ContentType: $ct, charset: $chs, err: ${ex.getMessage}"))) } } } diff --git a/src/main/scala/codec/Decoders.scala b/src/main/scala/codec/Decoders.scala index 702c019..17bbf66 100644 --- a/src/main/scala/codec/Decoders.scala +++ b/src/main/scala/codec/Decoders.scala @@ -32,7 +32,8 @@ object FDecoders { final def apply(c: HCursor): Decoder.Result[LocalDate] = { for { dateString <- c.as[String] - date <- Try(LocalDate.parse(dateString)).toEither.left.map(err => DecodingFailure(err.getMessage + " at " + c.focus.map(_.noSpaces), c.history)) + date <- Try(LocalDate.parse(dateString)).toEither.left.map(err => + DecodingFailure(err.getMessage + " at " + c.focus.map(_.noSpaces), c.history)) } yield date } } @@ -41,7 +42,8 @@ object FDecoders { final def apply(c: HCursor): Decoder.Result[Instant] = { for { dateString <- c.as[String] - date <- Try(Instant.parse(dateString)).toEither.left.map(err => DecodingFailure(err.getMessage + " at " + c.focus.map(_.noSpaces), c.history)) + date <- Try(Instant.parse(dateString)).toEither.left.map(err => + DecodingFailure(err.getMessage + " at " + c.focus.map(_.noSpaces), c.history)) } yield date } } @@ -49,44 +51,67 @@ object FDecoders { implicit val heroPortaitDecoder: Decoder[HeroPortrait] = Decoder.forProduct2("small", "medium")(HeroPortrait.apply) implicit def heroDecoder: Decoder[IdHero] = Decoder.forProduct5("id", "name", "role", "url", "portrait") { - (id: Long, name: String, role: Long, url: Uri, portrait: HeroPortrait) => (id, HeroF(name, role, url, portrait)) + (id: Long, name: String, role: Long, url: Uri, portrait: HeroPortrait) => + (id, HeroF(name, role, url, portrait)) } implicit def battlegroundDecoder: Decoder[IdBattleground] = Decoder.forProduct3("id", "name", "url") { - (id: Long, name: String, url: Uri) => (id, Battleground(name, url)) + (id: Long, name: String, url: Uri) => + (id, Battleground(name, url)) } - implicit def regionDecoder: Decoder[IdRegion] = Decoder.forProduct2("id", "name") { - (id: Long, name: String) => (id, Region(name)) + implicit def regionDecoder: Decoder[IdRegion] = Decoder.forProduct2("id", "name") { (id: Long, name: String) => + (id, Region(name)) } implicit def patchDecoder: Decoder[IdPatch] = Decoder.forProduct4("id", "name", "from_date", "to_date") { - (id: Long, name: String, fromDate: LocalDate, toDate: LocalDate) => (id, Patch(name, fromDate, toDate)) + (id: Long, name: String, fromDate: LocalDate, toDate: LocalDate) => + (id, Patch(name, fromDate, toDate)) } - implicit def teamLogoDecoder: Decoder[UrlTeamLogo] = Decoder.forProduct3("small", "medium", "big")(TeamLogoF.apply[Uri, Uri, Uri]) + implicit def teamLogoDecoder: Decoder[UrlTeamLogo] = + Decoder.forProduct3("small", "medium", "big")(TeamLogoF.apply[Uri, Uri, Uri]) implicit def teamDecoder: Decoder[IdTeam] = Decoder.forProduct5("id", "name", "region", "url", "logo") { - (id: Long, name: String, region: Long, url: Uri, logo: UrlTeamLogo) => (id, TeamF(name, region, url, logo)) + (id: Long, name: String, region: Long, url: Uri, logo: UrlTeamLogo) => + (id, TeamF(name, region, url, logo)) } - implicit def playerPhotoDecoder: Decoder[PlayerPhoto] = Decoder.forProduct3("small", "big", "medium")(PlayerPhoto.apply) - - implicit def playerDecoder: Decoder[IdPlayer] = Decoder.forProduct9("id", "team", "region", "nickname", "realname", "country", "role", "url", "photo") { - (id: Long, team: Option[Long], region: Long, nickname: String, realname: String, country: String, role: Int, url: Uri, photo: PlayerPhoto) => - ( - id, //TODO: Fix roles; if a role is added, this'll result in an un-nice pare error - PlayerF(team, region, nickname, realname, country, Roles.roles(role), url, photo) - ) - } + implicit def playerPhotoDecoder: Decoder[PlayerPhoto] = + Decoder.forProduct3("small", "big", "medium")(PlayerPhoto.apply) + + implicit def playerDecoder: Decoder[IdPlayer] = + Decoder.forProduct9("id", "team", "region", "nickname", "realname", "country", "role", "url", "photo") { + (id: Long, + team: Option[Long], + region: Long, + nickname: String, + realname: String, + country: String, + role: Int, + url: Uri, + photo: PlayerPhoto) => + ( + id, //TODO: Fix roles; if a role is added, this'll result in an un-nice pare error + PlayerF(team, region, nickname, realname, country, Roles.roles(role), url, photo) + ) + } implicit def tournamentStageDecoder: Decoder[IdTournamentStage] = Decoder.forProduct2("id", "name")( (id: Long, name: String) => (id, TournamentStage(name)) ) - implicit def tournamentDecoder: Decoder[IdTournament] = Decoder.forProduct7("id", "name", "start_date", "end_date", "region", "url", "stages") { - (id: Long, name: String, startDate: LocalDate, endDate: LocalDate, region: Option[Long], url: Uri, stages: List[IdTournamentStage]) => (id, TournamentF(name, startDate, endDate, region, url, stages)) - } + implicit def tournamentDecoder: Decoder[IdTournament] = + Decoder.forProduct7("id", "name", "start_date", "end_date", "region", "url", "stages") { + (id: Long, + name: String, + startDate: LocalDate, + endDate: LocalDate, + region: Option[Long], + url: Uri, + stages: List[IdTournamentStage]) => + (id, TournamentF(name, startDate, endDate, region, url, stages)) + } implicit def pickDecoder: Decoder[IdPick] = Decoder.forProduct2("hero", "player")(PickF.apply[Long, Long]) @@ -94,30 +119,56 @@ object FDecoders { DraftF.apply[Long, Long, IdPick] ) - implicit def matchDecoder: Decoder[IdMatch] = Decoder.forProduct11("id", "date", "patch", "tournament", "stage", "round", "series", "game", "map", "url", "drafts") { - (id: Long, date: Instant, patch: Long, tournament: Long, stage: Long, round: String, series: Long, game: Int, battleground: Long, url: Uri, drafts: List[DraftId]) => - (id, MatchF(date, patch, tournament, stage, round, series, game, battleground, url, drafts)) - } + implicit def matchDecoder: Decoder[IdMatch] = + Decoder.forProduct11("id", + "date", + "patch", + "tournament", + "stage", + "round", + "series", + "game", + "map", + "url", + "drafts") { + (id: Long, + date: Instant, + patch: Long, + tournament: Long, + stage: Long, + round: String, + series: Long, + game: Int, + battleground: Long, + url: Uri, + drafts: List[DraftId]) => + (id, MatchF(date, patch, tournament, stage, round, series, game, battleground, url, drafts)) + } import net.APIError - implicit def errorDecoder: Decoder[APIError] = Decoder.forProduct1("detail") { - (detail: String) => APIError(detail) + implicit def errorDecoder: Decoder[APIError] = Decoder.forProduct1("detail") { (detail: String) => + APIError(detail) } implicit def errorEncoder: Encoder[APIError] = Encoder.forProduct1("detail")(t => t.detail) import net.authorization.Token - implicit def tokenDecoder: Decoder[Token] = Decoder.forProduct1("token") { - (token: String) => Token(token) + implicit def tokenDecoder: Decoder[Token] = Decoder.forProduct1("token") { (token: String) => + Token(token) } implicit def tokenEncoder: Encoder[Token] = Encoder.forProduct1("token")(t => t.token) - implicit def decodeAPICall[A: Decoder]: Decoder[APIResultF[A, Uri @@ A]] = Decoder.forProduct4("count", "next", "previous", "results")(APIResultF.apply[A, Uri @@ A] _) - implicit def encodeAPICall[A: Encoder]: Encoder[APIResultF[A, Uri @@ A]] = Encoder.forProduct4("count", "next", "previous", "results")(u => (u.count, u.next, u.previous, u.results)) + implicit def decodeAPICall[A: Decoder]: Decoder[APIResultF[A, Uri @@ A]] = + Decoder.forProduct4("count", "next", "previous", "results")(APIResultF.apply[A, Uri @@ A] _) + implicit def encodeAPICall[A: Encoder]: Encoder[APIResultF[A, Uri @@ A]] = + Encoder.forProduct4("count", "next", "previous", "results")(u => (u.count, u.next, u.previous, u.results)) implicit def decodePlainArray[A: Decoder] = Decoder.decodeList[A] - implicit val decodeLiveStream: Decoder[LiveStream] = Decoder.forProduct4("country", "caster", "url", "viewers")(LiveStream.apply) - implicit val decodeCalendarMatch: Decoder[CalendarIdMatch] = Decoder.forProduct5("datetime", "name", "format", "left_team", "right_team")(CalendarMatchF.apply[Long, Long]) - implicit val decodeCalendarEntry: Decoder[CalendarEntryId] = Decoder.forProduct5("date", "stage", "is_live", "streams", "matches")(CalendarEntryF.apply[Long, Long, Long]) + implicit val decodeLiveStream: Decoder[LiveStream] = + Decoder.forProduct4("country", "caster", "url", "viewers")(LiveStream.apply) + implicit val decodeCalendarMatch: Decoder[CalendarIdMatch] = + Decoder.forProduct5("datetime", "name", "format", "left_team", "right_team")(CalendarMatchF.apply[Long, Long]) + implicit val decodeCalendarEntry: Decoder[CalendarEntryId] = + Decoder.forProduct5("date", "stage", "is_live", "streams", "matches")(CalendarEntryF.apply[Long, Long, Long]) -} \ No newline at end of file +} diff --git a/src/main/scala/data/ApiResultF.scala b/src/main/scala/data/ApiResultF.scala index bf88efe..611c0d8 100644 --- a/src/main/scala/data/ApiResultF.scala +++ b/src/main/scala/data/ApiResultF.scala @@ -1,38 +1,40 @@ -package masterleague4s -package data - -import spinoco.protocol.http.Uri -import cats._ -import cats.implicits._ - -case class APIResultF[A, B](count: Int, next: Option[B], previous: Option[Uri], results: List[A]) - -object APIResultF { - implicit def apiResultFunctor: Bitraverse[APIResultF] = new Bitraverse[APIResultF] { - override def bimap[A, B, C, D](fab: APIResultF[A, B])(f: (A) ⇒ C, g: (B) ⇒ D): APIResultF[C, D] = { - val newNext = fab.next.map(g) - val newResults = fab.results.map(f) - fab.copy(next = newNext, results = newResults) - } - - def bifoldLeft[A, B, C](fab: APIResultF[A, B], c: C)(f: (C, A) => C, g: (C, B) => C): C = { - val cc: C = fab.results.foldLeft(c)(f) - fab.next.foldLeft(cc)(g) - } - - def bifoldRight[A, B, C](fab: APIResultF[A, B], c: Eval[C])(f: (A, Eval[C]) => Eval[C], g: (B, Eval[C]) => Eval[C]): Eval[C] = { - val cc = fab.results.foldRight(c)(f) - fab.next.foldRight(cc)(g) - } - - def bitraverse[G[_]: Applicative, A, B, C, D](fab: APIResultF[A, B])(f: A => G[C], g: B => G[D]): G[APIResultF[C, D]] = { - val goc: G[List[C]] = fab.results.traverse(f) - val gld: G[Option[D]] = fab.next.traverse(g) - for { - pair <- goc.product(gld) - (oc, ld) = pair - } yield (fab.copy(results = oc, next = ld)) - } - } - -} \ No newline at end of file +package masterleague4s +package data + +import spinoco.protocol.http.Uri +import cats._ +import cats.implicits._ + +case class APIResultF[A, B](count: Int, next: Option[B], previous: Option[Uri], results: List[A]) + +object APIResultF { + implicit def apiResultFunctor: Bitraverse[APIResultF] = new Bitraverse[APIResultF] { + override def bimap[A, B, C, D](fab: APIResultF[A, B])(f: (A) => C, g: (B) => D): APIResultF[C, D] = { + val newNext = fab.next.map(g) + val newResults = fab.results.map(f) + fab.copy(next = newNext, results = newResults) + } + + def bifoldLeft[A, B, C](fab: APIResultF[A, B], c: C)(f: (C, A) => C, g: (C, B) => C): C = { + val cc: C = fab.results.foldLeft(c)(f) + fab.next.foldLeft(cc)(g) + } + + def bifoldRight[A, B, C](fab: APIResultF[A, B], c: Eval[C])(f: (A, Eval[C]) => Eval[C], + g: (B, Eval[C]) => Eval[C]): Eval[C] = { + val cc = fab.results.foldRight(c)(f) + fab.next.foldRight(cc)(g) + } + + def bitraverse[G[_]: Applicative, A, B, C, D](fab: APIResultF[A, B])(f: A => G[C], + g: B => G[D]): G[APIResultF[C, D]] = { + val goc: G[List[C]] = fab.results.traverse(f) + val gld: G[Option[D]] = fab.next.traverse(g) + for { + pair <- goc.product(gld) + (oc, ld) = pair + } yield (fab.copy(results = oc, next = ld)) + } + } + +} diff --git a/src/main/scala/data/Battleground.scala b/src/main/scala/data/Battleground.scala index 2821819..391539d 100644 --- a/src/main/scala/data/Battleground.scala +++ b/src/main/scala/data/Battleground.scala @@ -3,4 +3,4 @@ package data import spinoco.protocol.http.Uri -case class Battleground(name: String, url: Uri) \ No newline at end of file +case class Battleground(name: String, url: Uri) diff --git a/src/main/scala/data/CalendarEntryF.scala b/src/main/scala/data/CalendarEntryF.scala index a953411..778713b 100644 --- a/src/main/scala/data/CalendarEntryF.scala +++ b/src/main/scala/data/CalendarEntryF.scala @@ -3,8 +3,12 @@ package data import java.time.Instant -case class CalendarEntryF[A, B, C](date: Instant, stage: A, isLive: Boolean, streams: List[LiveStream], matches: List[CalendarMatchF[B, C]]) +case class CalendarEntryF[A, B, C](date: Instant, + stage: A, + isLive: Boolean, + streams: List[LiveStream], + matches: List[CalendarMatchF[B, C]]) object CalendarEntryF { //todo: Instances -} \ No newline at end of file +} diff --git a/src/main/scala/data/CalendarMatchF.scala b/src/main/scala/data/CalendarMatchF.scala index c0a680b..aaf8de4 100644 --- a/src/main/scala/data/CalendarMatchF.scala +++ b/src/main/scala/data/CalendarMatchF.scala @@ -8,4 +8,4 @@ case class CalendarMatchF[A, B](datetime: Instant, name: String, format: String, object CalendarMatchF { //TODO: Bifunctor instance -} \ No newline at end of file +} diff --git a/src/main/scala/data/DraftF.scala b/src/main/scala/data/DraftF.scala index 231b3ae..1e5e100 100644 --- a/src/main/scala/data/DraftF.scala +++ b/src/main/scala/data/DraftF.scala @@ -7,33 +7,41 @@ import cats._ case class DraftF[+A, +B, +C](team: A, isWinner: Boolean, bans: List[B], picks: List[C]) object DraftF { - def teamDraftFunctor[B, C]: Traverse[({ type l[a] = DraftF[a, B, C] })#l] = new Traverse[({ type l[a] = DraftF[a, B, C] })#l] { - override def map[A, AA](fa: DraftF[A, B, C])(f: A => AA) = { - val newTeam = f(fa.team) - fa.copy(team = newTeam) + def teamDraftFunctor[B, C]: Traverse[({ type l[a] = DraftF[a, B, C] })#l] = + new Traverse[({ type l[a] = DraftF[a, B, C] })#l] { + override def map[A, AA](fa: DraftF[A, B, C])(f: A => AA) = { + val newTeam = f(fa.team) + fa.copy(team = newTeam) + } + def foldLeft[A, AA](fa: DraftF[A, B, C], b: AA)(f: (AA, A) => AA): AA = f(b, fa.team) + def foldRight[A, AA](fa: DraftF[A, B, C], lb: Eval[AA])(f: (A, Eval[AA]) => Eval[AA]): Eval[AA] = f(fa.team, lb) + def traverse[G[_]: Applicative, A, AA](fa: DraftF[A, B, C])(f: A => G[AA]): G[DraftF[AA, B, C]] = + f(fa.team).map(b => fa.copy(team = b)) } - def foldLeft[A, AA](fa: DraftF[A, B, C], b: AA)(f: (AA, A) => AA): AA = f(b, fa.team) - def foldRight[A, AA](fa: DraftF[A, B, C], lb: Eval[AA])(f: (A, Eval[AA]) => Eval[AA]): Eval[AA] = f(fa.team, lb) - def traverse[G[_]: Applicative, A, AA](fa: DraftF[A, B, C])(f: A => G[AA]): G[DraftF[AA, B, C]] = f(fa.team).map(b => fa.copy(team = b)) - } - def bansDraftFunctor[A, C]: Traverse[({ type l[a] = DraftF[A, a, C] })#l] = new Traverse[({ type l[a] = DraftF[A, a, C] })#l] { - override def map[B, BB](fa: DraftF[A, B, C])(f: B => BB) = { - val newBans = fa.bans.map(f) - fa.copy(bans = newBans) + def bansDraftFunctor[A, C]: Traverse[({ type l[a] = DraftF[A, a, C] })#l] = + new Traverse[({ type l[a] = DraftF[A, a, C] })#l] { + override def map[B, BB](fa: DraftF[A, B, C])(f: B => BB) = { + val newBans = fa.bans.map(f) + fa.copy(bans = newBans) + } + def foldLeft[B, BB](fa: DraftF[A, B, C], b: BB)(f: (BB, B) => BB): BB = fa.bans.foldLeft(b)(f) + def foldRight[B, BB](fa: DraftF[A, B, C], lb: Eval[BB])(f: (B, Eval[BB]) => Eval[BB]): Eval[BB] = + fa.bans.foldRight(lb)(f) + def traverse[G[_]: Applicative, B, BB](fa: DraftF[A, B, C])(f: B => G[BB]): G[DraftF[A, BB, C]] = + fa.bans.traverse(f).map(newBans => fa.copy(bans = newBans)) } - def foldLeft[B, BB](fa: DraftF[A, B, C], b: BB)(f: (BB, B) => BB): BB = fa.bans.foldLeft(b)(f) - def foldRight[B, BB](fa: DraftF[A, B, C], lb: Eval[BB])(f: (B, Eval[BB]) => Eval[BB]): Eval[BB] = fa.bans.foldRight(lb)(f) - def traverse[G[_]: Applicative, B, BB](fa: DraftF[A, B, C])(f: B => G[BB]): G[DraftF[A, BB, C]] = fa.bans.traverse(f).map(newBans => fa.copy(bans = newBans)) - } - def picksDraftFunctor[A, B]: Traverse[({ type l[a] = DraftF[A, B, a] })#l] = new Traverse[({ type l[a] = DraftF[A, B, a] })#l] { - override def map[C, CC](fa: DraftF[A, B, C])(f: C => CC) = { - val newPicks = fa.picks.map(f) - fa.copy(picks = newPicks) + def picksDraftFunctor[A, B]: Traverse[({ type l[a] = DraftF[A, B, a] })#l] = + new Traverse[({ type l[a] = DraftF[A, B, a] })#l] { + override def map[C, CC](fa: DraftF[A, B, C])(f: C => CC) = { + val newPicks = fa.picks.map(f) + fa.copy(picks = newPicks) + } + def foldLeft[C, CC](fa: DraftF[A, B, C], b: CC)(f: (CC, C) => CC): CC = fa.picks.foldLeft(b)(f) + def foldRight[C, CC](fa: DraftF[A, B, C], lb: Eval[CC])(f: (C, Eval[CC]) => Eval[CC]): Eval[CC] = + fa.picks.foldRight(lb)(f) + def traverse[G[_]: Applicative, C, CC](fa: DraftF[A, B, C])(f: C => G[CC]): G[DraftF[A, B, CC]] = + fa.picks.traverse(f).map(newPicks => fa.copy(picks = newPicks)) } - def foldLeft[C, CC](fa: DraftF[A, B, C], b: CC)(f: (CC, C) => CC): CC = fa.picks.foldLeft(b)(f) - def foldRight[C, CC](fa: DraftF[A, B, C], lb: Eval[CC])(f: (C, Eval[CC]) => Eval[CC]): Eval[CC] = fa.picks.foldRight(lb)(f) - def traverse[G[_]: Applicative, C, CC](fa: DraftF[A, B, C])(f: C => G[CC]): G[DraftF[A, B, CC]] = fa.picks.traverse(f).map(newPicks => fa.copy(picks = newPicks)) - } -} \ No newline at end of file +} diff --git a/src/main/scala/data/HeroF.scala b/src/main/scala/data/HeroF.scala index e97cfc3..a5d9cc4 100644 --- a/src/main/scala/data/HeroF.scala +++ b/src/main/scala/data/HeroF.scala @@ -13,17 +13,18 @@ object HeroF { val newRole = f(fa.role) fa.copy(role = newRole) } - def foldLeft[A, B](fa: HeroF[A], b: B)(f: (B, A) => B): B = f(b, fa.role) + def foldLeft[A, B](fa: HeroF[A], b: B)(f: (B, A) => B): B = f(b, fa.role) def foldRight[A, B](fa: HeroF[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = f(fa.role, lb) - def traverse[G[_]: Applicative, A, B](fa: HeroF[A])(f: A => G[B]): G[HeroF[B]] = f(fa.role).map(b => fa.map(_ => b)) + def traverse[G[_]: Applicative, A, B](fa: HeroF[A])(f: A => G[B]): G[HeroF[B]] = + f(fa.role).map(b => fa.map(_ => b)) } implicit def heroEq[A: Eq] = new Eq[HeroF[A]] { def eqv(x: HeroF[A], y: HeroF[A]) = { x.name == y.name && - Eq.eqv(x.role, y.role) && - x.url == y.url && - x.portrait == y.portrait + Eq.eqv(x.role, y.role) && + x.url == y.url && + x.portrait == y.portrait } } -} \ No newline at end of file +} diff --git a/src/main/scala/data/LiveStream.scala b/src/main/scala/data/LiveStream.scala index e2389a5..8526185 100644 --- a/src/main/scala/data/LiveStream.scala +++ b/src/main/scala/data/LiveStream.scala @@ -2,4 +2,4 @@ package masterleague4s package data import spinoco.protocol.http.Uri -case class LiveStream(country: String, caster: String, url: Uri, viewers: Int) \ No newline at end of file +case class LiveStream(country: String, caster: String, url: Uri, viewers: Int) diff --git a/src/main/scala/data/MatchF.scala b/src/main/scala/data/MatchF.scala index 14ffded..4b7f33a 100644 --- a/src/main/scala/data/MatchF.scala +++ b/src/main/scala/data/MatchF.scala @@ -6,85 +6,112 @@ import java.time.Instant import cats.implicits._ import cats._ -case class MatchF[+A, +B, +C, +D, +E, +F](date: Instant, patch: A, tournament: B, stage: C, round: String, series: D, game: Int, battleground: E, url: Uri, drafts: List[F]) +case class MatchF[+A, +B, +C, +D, +E, +F](date: Instant, + patch: A, + tournament: B, + stage: C, + round: String, + series: D, + game: Int, + battleground: E, + url: Uri, + drafts: List[F]) object MatchF { - def patchHoleFunctor[B, C, D, E, F]: Traverse[({ type l[a] = MatchF[a, B, C, D, E, F] })#l] = new Traverse[({ type l[a] = MatchF[a, B, C, D, E, F] })#l] { - override def map[A, BB](fa: MatchF[A, B, C, D, E, F])(f: A => BB) = { - val newPatch = f(fa.patch) - fa.copy(patch = newPatch) + def patchHoleFunctor[B, C, D, E, F]: Traverse[({ type l[a] = MatchF[a, B, C, D, E, F] })#l] = + new Traverse[({ type l[a] = MatchF[a, B, C, D, E, F] })#l] { + override def map[A, BB](fa: MatchF[A, B, C, D, E, F])(f: A => BB) = { + val newPatch = f(fa.patch) + fa.copy(patch = newPatch) + } + def foldLeft[A, BB](fa: MatchF[A, B, C, D, E, F], b: BB)(f: (BB, A) => BB): BB = f(b, fa.patch) + def foldRight[A, BB](fa: MatchF[A, B, C, D, E, F], lb: Eval[BB])(f: (A, Eval[BB]) => Eval[BB]): Eval[BB] = + f(fa.patch, lb) + def traverse[G[_]: Applicative, A, BB](fa: MatchF[A, B, C, D, E, F])( + f: A => G[BB]): G[MatchF[BB, B, C, D, E, F]] = { + f(fa.patch).map(b => map(fa)(_ => b)) + } } - def foldLeft[A, BB](fa: MatchF[A, B, C, D, E, F], b: BB)(f: (BB, A) => BB): BB = f(b, fa.patch) - def foldRight[A, BB](fa: MatchF[A, B, C, D, E, F], lb: Eval[BB])(f: (A, Eval[BB]) => Eval[BB]): Eval[BB] = f(fa.patch, lb) - def traverse[G[_]: Applicative, A, BB](fa: MatchF[A, B, C, D, E, F])(f: A => G[BB]): G[MatchF[BB, B, C, D, E, F]] = { - f(fa.patch).map(b => map(fa)(_ => b)) - } - } - def tournamentHoleFunctor[A, C, D, E, F]: Traverse[({ type l[a] = MatchF[A, a, C, D, E, F] })#l] = new Traverse[({ type l[a] = MatchF[A, a, C, D, E, F] })#l] { - override def map[AA, B](fa: MatchF[A, AA, C, D, E, F])(f: AA => B) = { - val newTournament = f(fa.tournament) - fa.copy(tournament = newTournament) - } - def foldLeft[AA, B](fa: MatchF[A, AA, C, D, E, F], b: B)(f: (B, AA) => B): B = f(b, fa.tournament) - def foldRight[AA, B](fa: MatchF[A, AA, C, D, E, F], lb: Eval[B])(f: (AA, Eval[B]) => Eval[B]): Eval[B] = f(fa.tournament, lb) - def traverse[G[_]: Applicative, AA, B](fa: MatchF[A, AA, C, D, E, F])(f: AA => G[B]): G[MatchF[A, B, C, D, E, F]] = { - f(fa.tournament).map(b => map(fa)(_ => b)) + def tournamentHoleFunctor[A, C, D, E, F]: Traverse[({ type l[a] = MatchF[A, a, C, D, E, F] })#l] = + new Traverse[({ type l[a] = MatchF[A, a, C, D, E, F] })#l] { + override def map[AA, B](fa: MatchF[A, AA, C, D, E, F])(f: AA => B) = { + val newTournament = f(fa.tournament) + fa.copy(tournament = newTournament) + } + def foldLeft[AA, B](fa: MatchF[A, AA, C, D, E, F], b: B)(f: (B, AA) => B): B = f(b, fa.tournament) + def foldRight[AA, B](fa: MatchF[A, AA, C, D, E, F], lb: Eval[B])(f: (AA, Eval[B]) => Eval[B]): Eval[B] = + f(fa.tournament, lb) + def traverse[G[_]: Applicative, AA, B](fa: MatchF[A, AA, C, D, E, F])( + f: AA => G[B]): G[MatchF[A, B, C, D, E, F]] = { + f(fa.tournament).map(b => map(fa)(_ => b)) + } } - } - def stageHoleFunctor[A, B, D, E, F]: Traverse[({ type l[a] = MatchF[A, B, a, D, E, F] })#l] = new Traverse[({ type l[a] = MatchF[A, B, a, D, E, F] })#l] { - override def map[AA, BB](fa: MatchF[A, B, AA, D, E, F])(f: AA => BB) = { - val newStage = f(fa.stage) - fa.copy(stage = newStage) + def stageHoleFunctor[A, B, D, E, F]: Traverse[({ type l[a] = MatchF[A, B, a, D, E, F] })#l] = + new Traverse[({ type l[a] = MatchF[A, B, a, D, E, F] })#l] { + override def map[AA, BB](fa: MatchF[A, B, AA, D, E, F])(f: AA => BB) = { + val newStage = f(fa.stage) + fa.copy(stage = newStage) + } + def foldLeft[AA, BB](fa: MatchF[A, B, AA, D, E, F], b: BB)(f: (BB, AA) => BB): BB = f(b, fa.stage) + def foldRight[AA, BB](fa: MatchF[A, B, AA, D, E, F], lb: Eval[BB])(f: (AA, Eval[BB]) => Eval[BB]): Eval[BB] = + f(fa.stage, lb) + def traverse[G[_]: Applicative, AA, BB](fa: MatchF[A, B, AA, D, E, F])( + f: AA => G[BB]): G[MatchF[A, B, BB, D, E, F]] = { + f(fa.stage).map(b => map(fa)(_ => b)) + } } - def foldLeft[AA, BB](fa: MatchF[A, B, AA, D, E, F], b: BB)(f: (BB, AA) => BB): BB = f(b, fa.stage) - def foldRight[AA, BB](fa: MatchF[A, B, AA, D, E, F], lb: Eval[BB])(f: (AA, Eval[BB]) => Eval[BB]): Eval[BB] = f(fa.stage, lb) - def traverse[G[_]: Applicative, AA, BB](fa: MatchF[A, B, AA, D, E, F])(f: AA => G[BB]): G[MatchF[A, B, BB, D, E, F]] = { - f(fa.stage).map(b => map(fa)(_ => b)) - } - } - def seriesHoleFunctor[A, B, C, E, F]: Traverse[({ type l[a] = MatchF[A, B, C, a, E, F] })#l] = new Traverse[({ type l[a] = MatchF[A, B, C, a, E, F] })#l] { - override def map[AA, BB](fa: MatchF[A, B, C, AA, E, F])(f: AA => BB) = { - val newSeries = f(fa.series) - fa.copy(series = newSeries) - } - def foldLeft[AA, BB](fa: MatchF[A, B, C, AA, E, F], b: BB)(f: (BB, AA) => BB): BB = f(b, fa.series) - def foldRight[AA, BB](fa: MatchF[A, B, C, AA, E, F], lb: Eval[BB])(f: (AA, Eval[BB]) => Eval[BB]): Eval[BB] = f(fa.series, lb) - def traverse[G[_]: Applicative, AA, BB](fa: MatchF[A, B, C, AA, E, F])(f: AA => G[BB]): G[MatchF[A, B, C, BB, E, F]] = { - f(fa.series).map(b => map(fa)(_ => b)) + def seriesHoleFunctor[A, B, C, E, F]: Traverse[({ type l[a] = MatchF[A, B, C, a, E, F] })#l] = + new Traverse[({ type l[a] = MatchF[A, B, C, a, E, F] })#l] { + override def map[AA, BB](fa: MatchF[A, B, C, AA, E, F])(f: AA => BB) = { + val newSeries = f(fa.series) + fa.copy(series = newSeries) + } + def foldLeft[AA, BB](fa: MatchF[A, B, C, AA, E, F], b: BB)(f: (BB, AA) => BB): BB = f(b, fa.series) + def foldRight[AA, BB](fa: MatchF[A, B, C, AA, E, F], lb: Eval[BB])(f: (AA, Eval[BB]) => Eval[BB]): Eval[BB] = + f(fa.series, lb) + def traverse[G[_]: Applicative, AA, BB](fa: MatchF[A, B, C, AA, E, F])( + f: AA => G[BB]): G[MatchF[A, B, C, BB, E, F]] = { + f(fa.series).map(b => map(fa)(_ => b)) + } } - } - def battlegroundHoleFunctor[A, B, C, D, F]: Traverse[({ type l[a] = MatchF[A, B, C, D, a, F] })#l] = new Traverse[({ type l[a] = MatchF[A, B, C, D, a, F] })#l] { - override def map[AA, BB](fa: MatchF[A, B, C, D, AA, F])(f: AA => BB) = { - val newMap = f(fa.battleground) - fa.copy(battleground = newMap) + def battlegroundHoleFunctor[A, B, C, D, F]: Traverse[({ type l[a] = MatchF[A, B, C, D, a, F] })#l] = + new Traverse[({ type l[a] = MatchF[A, B, C, D, a, F] })#l] { + override def map[AA, BB](fa: MatchF[A, B, C, D, AA, F])(f: AA => BB) = { + val newMap = f(fa.battleground) + fa.copy(battleground = newMap) + } + def foldLeft[AA, BB](fa: MatchF[A, B, C, D, AA, F], b: BB)(f: (BB, AA) => BB): BB = f(b, fa.battleground) + def foldRight[AA, BB](fa: MatchF[A, B, C, D, AA, F], lb: Eval[BB])(f: (AA, Eval[BB]) => Eval[BB]): Eval[BB] = + f(fa.battleground, lb) + def traverse[G[_]: Applicative, AA, BB](fa: MatchF[A, B, C, D, AA, F])( + f: AA => G[BB]): G[MatchF[A, B, C, D, BB, F]] = { + f(fa.battleground).map(b => map(fa)(_ => b)) + } } - def foldLeft[AA, BB](fa: MatchF[A, B, C, D, AA, F], b: BB)(f: (BB, AA) => BB): BB = f(b, fa.battleground) - def foldRight[AA, BB](fa: MatchF[A, B, C, D, AA, F], lb: Eval[BB])(f: (AA, Eval[BB]) => Eval[BB]): Eval[BB] = f(fa.battleground, lb) - def traverse[G[_]: Applicative, AA, BB](fa: MatchF[A, B, C, D, AA, F])(f: AA => G[BB]): G[MatchF[A, B, C, D, BB, F]] = { - f(fa.battleground).map(b => map(fa)(_ => b)) - } - } - def draftHoleFunctor[A, B, C, D, E]: Traverse[({ type l[a] = MatchF[A, B, C, D, E, a] })#l] = new Traverse[({ type l[a] = MatchF[A, B, C, D, E, a] })#l] { - override def map[AA, BB](fa: MatchF[A, B, C, D, E, AA])(f: AA => BB) = { - val newDrafts = fa.drafts.map(f) - fa.copy(drafts = newDrafts) - } + def draftHoleFunctor[A, B, C, D, E]: Traverse[({ type l[a] = MatchF[A, B, C, D, E, a] })#l] = + new Traverse[({ type l[a] = MatchF[A, B, C, D, E, a] })#l] { + override def map[AA, BB](fa: MatchF[A, B, C, D, E, AA])(f: AA => BB) = { + val newDrafts = fa.drafts.map(f) + fa.copy(drafts = newDrafts) + } - def foldLeft[AA, BB](fa: MatchF[A, B, C, D, E, AA], b: BB)(f: (BB, AA) => BB): BB = fa.drafts.foldLeft(b)(f) + def foldLeft[AA, BB](fa: MatchF[A, B, C, D, E, AA], b: BB)(f: (BB, AA) => BB): BB = fa.drafts.foldLeft(b)(f) - def foldRight[AA, BB](fa: MatchF[A, B, C, D, E, AA], lb: Eval[BB])(f: (AA, Eval[BB]) => Eval[BB]): Eval[BB] = fa.drafts.foldRight(lb)(f) //(fa.battleground, lb) + def foldRight[AA, BB](fa: MatchF[A, B, C, D, E, AA], lb: Eval[BB])(f: (AA, Eval[BB]) => Eval[BB]): Eval[BB] = + fa.drafts.foldRight(lb)(f) //(fa.battleground, lb) - def traverse[G[_]: Applicative, AA, BB](fa: MatchF[A, B, C, D, E, AA])(f: AA => G[BB]): G[MatchF[A, B, C, D, E, BB]] = { - fa.drafts.traverse(f).map(newDrafts => fa.copy(drafts = newDrafts)) + def traverse[G[_]: Applicative, AA, BB](fa: MatchF[A, B, C, D, E, AA])( + f: AA => G[BB]): G[MatchF[A, B, C, D, E, BB]] = { + fa.drafts.traverse(f).map(newDrafts => fa.copy(drafts = newDrafts)) - } + } - } + } -} \ No newline at end of file +} diff --git a/src/main/scala/data/Patch.scala b/src/main/scala/data/Patch.scala index 7c01638..079d404 100644 --- a/src/main/scala/data/Patch.scala +++ b/src/main/scala/data/Patch.scala @@ -3,4 +3,4 @@ package data import java.time.LocalDate -case class Patch(name: String, fromDate: LocalDate, toDate: LocalDate) \ No newline at end of file +case class Patch(name: String, fromDate: LocalDate, toDate: LocalDate) diff --git a/src/main/scala/data/PickF.scala b/src/main/scala/data/PickF.scala index e969584..1a63fa2 100644 --- a/src/main/scala/data/PickF.scala +++ b/src/main/scala/data/PickF.scala @@ -5,4 +5,4 @@ case class PickF[+A, +B](hero: A, player: B) object PickF { //todo: Implement bitraverse. Can this be done from tuple2? -} \ No newline at end of file +} diff --git a/src/main/scala/data/PlayerF.scala b/src/main/scala/data/PlayerF.scala index b8de819..812840b 100644 --- a/src/main/scala/data/PlayerF.scala +++ b/src/main/scala/data/PlayerF.scala @@ -5,44 +5,57 @@ import spinoco.protocol.http.Uri import cats.implicits._ import cats._ -case class PlayerF[A, B, C](team: Option[A], region: B, nickname: String, realname: String, country: String, role: C, url: Uri, photo: PlayerPhoto) +case class PlayerF[A, B, C](team: Option[A], + region: B, + nickname: String, + realname: String, + country: String, + role: C, + url: Uri, + photo: PlayerPhoto) object PlayerF { - def teamPlayerHoleFunctor[B, C]: Traverse[({ type l[a] = PlayerF[a, B, C] })#l] = new Traverse[({ type l[a] = PlayerF[a, B, C] })#l] { - override def map[A, AA](fa: PlayerF[A, B, C])(f: A => AA) = { - val newTeam = fa.team.map(f) - fa.copy(team = newTeam) + def teamPlayerHoleFunctor[B, C]: Traverse[({ type l[a] = PlayerF[a, B, C] })#l] = + new Traverse[({ type l[a] = PlayerF[a, B, C] })#l] { + override def map[A, AA](fa: PlayerF[A, B, C])(f: A => AA) = { + val newTeam = fa.team.map(f) + fa.copy(team = newTeam) + } + + def foldLeft[A, BB](fa: PlayerF[A, B, C], b: BB)(f: (BB, A) => BB): BB = fa.team.foldLeft(b)(f) + def foldRight[A, BB](fa: PlayerF[A, B, C], lb: Eval[BB])(f: (A, Eval[BB]) => Eval[BB]): Eval[BB] = + fa.team.foldRight(lb)(f) + def traverse[G[_]: Applicative, A, AA](fa: PlayerF[A, B, C])(f: A => G[AA]): G[PlayerF[AA, B, C]] = { + fa.team.traverse(f).map(newTeam => fa.copy(team = newTeam)) + } } - def foldLeft[A, BB](fa: PlayerF[A, B, C], b: BB)(f: (BB, A) => BB): BB = fa.team.foldLeft(b)(f) - def foldRight[A, BB](fa: PlayerF[A, B, C], lb: Eval[BB])(f: (A, Eval[BB]) => Eval[BB]): Eval[BB] = fa.team.foldRight(lb)(f) - def traverse[G[_]: Applicative, A, AA](fa: PlayerF[A, B, C])(f: A => G[AA]): G[PlayerF[AA, B, C]] = { - fa.team.traverse(f).map(newTeam => fa.copy(team = newTeam)) + def regionPlayerHoleFunctor[A, C]: Traverse[({ type l[a] = PlayerF[A, a, C] })#l] = + new Traverse[({ type l[a] = PlayerF[A, a, C] })#l] { + override def map[B, BB](fa: PlayerF[A, B, C])(f: B => BB) = { + val newRegion = f(fa.region) + fa.copy(region = newRegion) + } + + def foldLeft[B, BB](fa: PlayerF[A, B, C], b: BB)(f: (BB, B) => BB): BB = f(b, fa.region) + def foldRight[B, BB](fa: PlayerF[A, B, C], lb: Eval[BB])(f: (B, Eval[BB]) => Eval[BB]): Eval[BB] = + f(fa.region, lb) + def traverse[G[_]: Applicative, B, BB](fa: PlayerF[A, B, C])(f: B => G[BB]): G[PlayerF[A, BB, C]] = + f(fa.region).map(b => fa.copy(region = b)) } - } - def regionPlayerHoleFunctor[A, C]: Traverse[({ type l[a] = PlayerF[A, a, C] })#l] = new Traverse[({ type l[a] = PlayerF[A, a, C] })#l] { - override def map[B, BB](fa: PlayerF[A, B, C])(f: B => BB) = { - val newRegion = f(fa.region) - fa.copy(region = newRegion) + def rolePlayerHoleFunctor[A, B]: Traverse[({ type l[a] = PlayerF[A, B, a] })#l] = + new Traverse[({ type l[a] = PlayerF[A, B, a] })#l] { + override def map[C, CC](fa: PlayerF[A, B, C])(f: C => CC) = { + val newRole = f(fa.role) + fa.copy(role = newRole) + } + + def foldLeft[C, CC](fa: PlayerF[A, B, C], b: CC)(f: (CC, C) => CC): CC = f(b, fa.role) + def foldRight[C, CC](fa: PlayerF[A, B, C], lb: Eval[CC])(f: (C, Eval[CC]) => Eval[CC]): Eval[CC] = f(fa.role, lb) + def traverse[G[_]: Applicative, C, CC](fa: PlayerF[A, B, C])(f: C => G[CC]): G[PlayerF[A, B, CC]] = { + f(fa.role).map(b => fa.copy(role = b)) + } } - - def foldLeft[B, BB](fa: PlayerF[A, B, C], b: BB)(f: (BB, B) => BB): BB = f(b, fa.region) - def foldRight[B, BB](fa: PlayerF[A, B, C], lb: Eval[BB])(f: (B, Eval[BB]) => Eval[BB]): Eval[BB] = f(fa.region, lb) - def traverse[G[_]: Applicative, B, BB](fa: PlayerF[A, B, C])(f: B => G[BB]): G[PlayerF[A, BB, C]] = f(fa.region).map(b => fa.copy(region = b)) - } - - def rolePlayerHoleFunctor[A, B]: Traverse[({ type l[a] = PlayerF[A, B, a] })#l] = new Traverse[({ type l[a] = PlayerF[A, B, a] })#l] { - override def map[C, CC](fa: PlayerF[A, B, C])(f: C => CC) = { - val newRole = f(fa.role) - fa.copy(role = newRole) - } - - def foldLeft[C, CC](fa: PlayerF[A, B, C], b: CC)(f: (CC, C) => CC): CC = f(b, fa.role) - def foldRight[C, CC](fa: PlayerF[A, B, C], lb: Eval[CC])(f: (C, Eval[CC]) => Eval[CC]): Eval[CC] = f(fa.role, lb) - def traverse[G[_]: Applicative, C, CC](fa: PlayerF[A, B, C])(f: C => G[CC]): G[PlayerF[A, B, CC]] = { - f(fa.role).map(b => fa.copy(role = b)) - } - } -} \ No newline at end of file +} diff --git a/src/main/scala/data/PlayerPhoto.scala b/src/main/scala/data/PlayerPhoto.scala index 5feea6c..5057856 100644 --- a/src/main/scala/data/PlayerPhoto.scala +++ b/src/main/scala/data/PlayerPhoto.scala @@ -4,4 +4,4 @@ package data import spinoco.protocol.http.Uri //todo: F-ify -case class PlayerPhoto(small: Uri, big: Uri, medium: Uri) \ No newline at end of file +case class PlayerPhoto(small: Uri, big: Uri, medium: Uri) diff --git a/src/main/scala/data/Region.scala b/src/main/scala/data/Region.scala index 62da99f..56639c7 100644 --- a/src/main/scala/data/Region.scala +++ b/src/main/scala/data/Region.scala @@ -1,4 +1,4 @@ package masterleague4s package data -case class Region(name: String) \ No newline at end of file +case class Region(name: String) diff --git a/src/main/scala/data/Roles.scala b/src/main/scala/data/Roles.scala index 2ebb822..53a7175 100644 --- a/src/main/scala/data/Roles.scala +++ b/src/main/scala/data/Roles.scala @@ -31,6 +31,5 @@ object Roles { Support -> 2, Assassin -> 3, Specialist -> 4 - ) -} \ No newline at end of file +} diff --git a/src/main/scala/data/TeamF.scala b/src/main/scala/data/TeamF.scala index f532154..3ec7fdf 100644 --- a/src/main/scala/data/TeamF.scala +++ b/src/main/scala/data/TeamF.scala @@ -8,15 +8,18 @@ import cats._ case class TeamF[A, B, C, D](name: String, region: A, url: Uri, logo: TeamLogoF[B, C, D]) object TeamF { - implicit def teamFunctor[B, C, D]: Traverse[({ type l[a] = TeamF[a, B, C, D] })#l] = new Traverse[({ type l[a] = TeamF[a, B, C, D] })#l] { - override def map[A, AA](fa: TeamF[A, B, C, D])(f: A => AA) = { - val newRegion = f(fa.region) - fa.copy(region = newRegion) + implicit def teamFunctor[B, C, D]: Traverse[({ type l[a] = TeamF[a, B, C, D] })#l] = + new Traverse[({ type l[a] = TeamF[a, B, C, D] })#l] { + override def map[A, AA](fa: TeamF[A, B, C, D])(f: A => AA) = { + val newRegion = f(fa.region) + fa.copy(region = newRegion) + } + def foldLeft[A, AA](fa: TeamF[A, B, C, D], b: AA)(f: (AA, A) => AA): AA = f(b, fa.region) + def foldRight[A, AA](fa: TeamF[A, B, C, D], lb: Eval[AA])(f: (A, Eval[AA]) => Eval[AA]): Eval[AA] = + f(fa.region, lb) + def traverse[G[_]: Applicative, A, AA](fa: TeamF[A, B, C, D])(f: A => G[AA]): G[TeamF[AA, B, C, D]] = + f(fa.region).map(b => fa.copy(region = b)) } - def foldLeft[A, AA](fa: TeamF[A, B, C, D], b: AA)(f: (AA, A) => AA): AA = f(b, fa.region) - def foldRight[A, AA](fa: TeamF[A, B, C, D], lb: Eval[AA])(f: (A, Eval[AA]) => Eval[AA]): Eval[AA] = f(fa.region, lb) - def traverse[G[_]: Applicative, A, AA](fa: TeamF[A, B, C, D])(f: A => G[AA]): G[TeamF[AA, B, C, D]] = f(fa.region).map(b => fa.copy(region = b)) - } //TODO: Add logo functors -} \ No newline at end of file +} diff --git a/src/main/scala/data/TeamLogo.scala b/src/main/scala/data/TeamLogo.scala index 425d86c..92f0783 100644 --- a/src/main/scala/data/TeamLogo.scala +++ b/src/main/scala/data/TeamLogo.scala @@ -5,4 +5,4 @@ case class TeamLogoF[A, B, C](small: A, medium: B, large: C) object TeamLogoF { /* Todo: instances */ -} \ No newline at end of file +} diff --git a/src/main/scala/data/TournamentF.scala b/src/main/scala/data/TournamentF.scala index 0ada4e2..90e27b5 100644 --- a/src/main/scala/data/TournamentF.scala +++ b/src/main/scala/data/TournamentF.scala @@ -6,7 +6,12 @@ import java.time.LocalDate import cats.implicits._ import cats._ -case class TournamentF[A, B](name: String, startDate: LocalDate, endDate: LocalDate, region: Option[A], url: Uri, stages: List[B]) +case class TournamentF[A, B](name: String, + startDate: LocalDate, + endDate: LocalDate, + region: Option[A], + url: Uri, + stages: List[B]) object TournamentF { implicit def tournementBiFunctor: Bitraverse[TournamentF] = new Bitraverse[TournamentF] { @@ -18,26 +23,28 @@ object TournamentF { def bifoldLeft[A, B, C](fab: TournamentF[A, B], c: C)(f: (C, A) => C, g: (C, B) => C): C = { val fed: Option[C] = fab.region.map(r => f(c, r)) fed match { - case None => fab.stages.foldLeft(c)(g) + case None => fab.stages.foldLeft(c)(g) case Some(cc) => fab.stages.foldLeft(cc)(g) } } - def bifoldRight[A, B, C](fab: TournamentF[A, B], c: Eval[C])(f: (A, Eval[C]) => Eval[C], g: (B, Eval[C]) => Eval[C]): Eval[C] = { + def bifoldRight[A, B, C](fab: TournamentF[A, B], c: Eval[C])(f: (A, Eval[C]) => Eval[C], + g: (B, Eval[C]) => Eval[C]): Eval[C] = { val fed = fab.region.map(r => f(r, c)) fed match { - case None => fab.stages.foldRight(c)(g) + case None => fab.stages.foldRight(c)(g) case Some(cc) => fab.stages.foldRight(cc)(g) } } - def bitraverse[G[_]: Applicative, A, B, C, D](fab: TournamentF[A, B])(f: A => G[C], g: B => G[D]): G[TournamentF[C, D]] = { + def bitraverse[G[_]: Applicative, A, B, C, D](fab: TournamentF[A, B])(f: A => G[C], + g: B => G[D]): G[TournamentF[C, D]] = { val goc: G[Option[C]] = fab.region.traverse(f) - val gld: G[List[D]] = fab.stages.traverse(g) + val gld: G[List[D]] = fab.stages.traverse(g) for { pair <- goc.product(gld) (oc, ld) = pair } yield (fab.copy(region = oc, stages = ld)) } } -} \ No newline at end of file +} diff --git a/src/main/scala/data/TournamentStage.scala b/src/main/scala/data/TournamentStage.scala index c591cd8..d5fc39c 100644 --- a/src/main/scala/data/TournamentStage.scala +++ b/src/main/scala/data/TournamentStage.scala @@ -1,4 +1,4 @@ package masterleague4s package data -case class TournamentStage(name: String) \ No newline at end of file +case class TournamentStage(name: String) diff --git a/src/main/scala/fix/Deep.scala b/src/main/scala/fix/Deep.scala index 7056536..391a285 100644 --- a/src/main/scala/fix/Deep.scala +++ b/src/main/scala/fix/Deep.scala @@ -6,11 +6,11 @@ object Deep { import Roles.Role import spinoco.protocol.http.Uri - type Hero = HeroF[Role] - type Team = TeamF[Region, Uri, Uri, Uri] - type Player = PlayerF[Team, Region, Role] + type Hero = HeroF[Role] + type Team = TeamF[Region, Uri, Uri, Uri] + type Player = PlayerF[Team, Region, Role] type Tournament = TournamentF[Region, TournamentStage] - type Pick = PickF[Hero, Player] - type Draft = DraftF[Team, Hero, Hero] - type Game = MatchF[Patch, Tournament, TournamentStage, Long, Battleground, Draft] -} \ No newline at end of file + type Pick = PickF[Hero, Player] + type Draft = DraftF[Team, Hero, Hero] + type Game = MatchF[Patch, Tournament, TournamentStage, Long, Battleground, Draft] +} diff --git a/src/main/scala/fix/IdAnnotated.scala b/src/main/scala/fix/IdAnnotated.scala index 032a4fd..edbd57c 100644 --- a/src/main/scala/fix/IdAnnotated.scala +++ b/src/main/scala/fix/IdAnnotated.scala @@ -7,25 +7,24 @@ object Serialized { import spinoco.protocol.http.Uri import shapeless.tag.@@ - type IdHero = (Long, HeroF[Long]) - type IdPlayer = (Long, PlayerF[Long, Long, Role]) - type IdPick = PickF[Long, Long] - type DraftId = DraftF[Long, Long, IdPick] - type IdMatch = (Long, MatchF[Long, Long, Long, Long, Long, DraftId]) - type IdBattleground = (Long, Battleground) - type IdRegion = (Long, Region) - type IdPatch = (Long, Patch) - type UrlTeamLogo = TeamLogoF[Uri, Uri, Uri] - type IdTeam = (Long, TeamF[Long, Uri, Uri, Uri]) + type IdHero = (Long, HeroF[Long]) + type IdPlayer = (Long, PlayerF[Long, Long, Role]) + type IdPick = PickF[Long, Long] + type DraftId = DraftF[Long, Long, IdPick] + type IdMatch = (Long, MatchF[Long, Long, Long, Long, Long, DraftId]) + type IdBattleground = (Long, Battleground) + type IdRegion = (Long, Region) + type IdPatch = (Long, Patch) + type UrlTeamLogo = TeamLogoF[Uri, Uri, Uri] + type IdTeam = (Long, TeamF[Long, Uri, Uri, Uri]) type IdTournamentStage = (Long, TournamentStage) - type IdTournament = (Long, TournamentF[Long, IdTournamentStage]) - type CalendarEntryId = CalendarEntryF[Long, Long, Long] - type CalendarIdMatch = CalendarMatchF[Long, Long] - type UriApiResult[A] = APIResultF[A, Uri @@ A] + type IdTournament = (Long, TournamentF[Long, IdTournamentStage]) + type CalendarEntryId = CalendarEntryF[Long, Long, Long] + type CalendarIdMatch = CalendarMatchF[Long, Long] + type UriApiResult[A] = APIResultF[A, Uri @@ A] implicit def idforTuple2[A] = new WithId[Tuple2[Long, A]] { def id(tup: Tuple2[Long, A]) = tup._1 } } - diff --git a/src/main/scala/fix/WithId.scala b/src/main/scala/fix/WithId.scala index 2b4772e..d522535 100644 --- a/src/main/scala/fix/WithId.scala +++ b/src/main/scala/fix/WithId.scala @@ -3,6 +3,7 @@ package data import simulacrum._ -@typeclass trait WithId[A] { +@typeclass +trait WithId[A] { def id(a: A): Long -} \ No newline at end of file +} diff --git a/src/main/scala/net/Auth.scala b/src/main/scala/net/Auth.scala index fd9fab6..c97fd12 100644 --- a/src/main/scala/net/Auth.scala +++ b/src/main/scala/net/Auth.scala @@ -1,34 +1,37 @@ -package masterleague4s -package net -package authorization - -import spinoco.protocol.http.Uri -import spinoco.protocol.http.Uri.Query -import spinoco.fs2.http.body.BodyEncoder -import spinoco.fs2.http.HttpClient -import spinoco.fs2.http.HttpRequest - -import fs2.Stream -import fs2._ -import fs2.util.syntax._ -import cats.implicits._ -import codec.FDecoders._ -import codec.CirceSupport._ - -object Auth { - - def getToken[F[_]: fs2.util.Catchable: fs2.util.Functor](endpoint: Uri, username: String, password: String): ClientRunnable[F, Stream[F, Token]] = { - implicit val encoder = BodyEncoder.`x-www-form-urlencoded` - - val run: HttpClient[F] => Stream[F, Token] = (client: HttpClient[F]) => for { - response <- client.request(HttpRequest.post(endpoint, Query.empty :+ ("username" -> username) :+ ("password") -> password)) - token <- Stream.eval(response.bodyAs[Token].map(_.require)) - } yield token - - ClientRunnable.lift(run) - - } - - def authheader(tok: Token): TokenAuthorization = TokenAuthorization(tok) -} - +package masterleague4s +package net +package authorization + +import spinoco.protocol.http.Uri +import spinoco.protocol.http.Uri.Query +import spinoco.fs2.http.body.BodyEncoder +import spinoco.fs2.http.HttpClient +import spinoco.fs2.http.HttpRequest + +import fs2.Stream +import fs2._ +import fs2.util.syntax._ +import cats.implicits._ +import codec.FDecoders._ +import codec.CirceSupport._ + +object Auth { + + def getToken[F[_]: fs2.util.Catchable: fs2.util.Functor](endpoint: Uri, + username: String, + password: String): ClientRunnable[F, Stream[F, Token]] = { + implicit val encoder = BodyEncoder.`x-www-form-urlencoded` + + val run: HttpClient[F] => Stream[F, Token] = (client: HttpClient[F]) => + for { + response <- client.request( + HttpRequest.post(endpoint, Query.empty :+ ("username" -> username) :+ ("password") -> password)) + token <- Stream.eval(response.bodyAs[Token].map(_.require)) + } yield token + + ClientRunnable.lift(run) + + } + + def authheader(tok: Token): TokenAuthorization = TokenAuthorization(tok) +} diff --git a/src/main/scala/net/Bridge.scala b/src/main/scala/net/Bridge.scala index 8c4919d..6b11a6a 100644 --- a/src/main/scala/net/Bridge.scala +++ b/src/main/scala/net/Bridge.scala @@ -18,17 +18,18 @@ object Bridge { def getRequests[A](res: UriApiResult[A]): Stream[Task, Uri @@ A] = Stream.emits(res.next.toList) - def getEntries[F[_], E](uri: Uri @@ E)(implicit ctch: Catchable[F], decoder: Decoder[E]): HttpClient[F] => Stream[F, Attempt[APIResultF[E, Uri @@ E]]] = + def getEntries[F[_], E](uri: Uri @@ E)( + implicit ctch: Catchable[F], + decoder: Decoder[E]): HttpClient[F] => Stream[F, Attempt[APIResultF[E, Uri @@ E]]] = client => { val r = HttpRequest.get[F](uri) client.request(r).flatMap { resp => { implicit val bodyDecoder = circeDecoder[UriApiResult[E]](decodeAPICall) - val fBody = resp.bodyAs[UriApiResult[E]] + val fBody = resp.bodyAs[UriApiResult[E]] Stream.eval(fBody) } } } } - diff --git a/src/main/scala/net/ClientRunnable.scala b/src/main/scala/net/ClientRunnable.scala index 03f7d59..7b839ac 100644 --- a/src/main/scala/net/ClientRunnable.scala +++ b/src/main/scala/net/ClientRunnable.scala @@ -1,44 +1,44 @@ -package masterleague4s -package net - -import spinoco.fs2.http -import http._ -import cats._ -import cats.implicits._ - -sealed trait ClientRunnable[F[_], +A] { - def run(client: HttpClient[F]): A -} - -case class PureRunnable[F[_], A](result: A) extends ClientRunnable[F, A] { - def run(client: HttpClient[F]) = result -} -case class LiftedRunnable[F[_], A](f: HttpClient[F] => A) extends ClientRunnable[F, A] { - def run(client: HttpClient[F]) = f(client) -} - -object ClientRunnable { - def lift[F[_], A](f: HttpClient[F] => A): ClientRunnable[F, A] = LiftedRunnable(f) - - def transform[F[_]]: (({ type l[a] = ClientRunnable[F, a] })#l ~> ({ type l[a] = Function1[HttpClient[F], a] })#l) = - new (({ type l[a] = ClientRunnable[F, a] })#l ~> ({ type l[a] = Function1[HttpClient[F], a] })#l) { - def apply[A](fa: ClientRunnable[F, A]): HttpClient[F] => A = fa.run _ - } - - class ClientRunnableMonad[F[_]] extends Monad[({ type l[a] = ClientRunnable[F, a] })#l] { - - def flatMap[A, B](fa: ClientRunnable[F, A])(f: A => ClientRunnable[F, B]): ClientRunnable[F, B] = { - ClientRunnable.lift((client: HttpClient[F]) => f(fa.run(client)).run(client)) - } - - def tailRecM[A, B](a: A)(fn: A => ClientRunnable[F, Either[A, B]]): ClientRunnable[F, B] = { - val m = cats.instances.all.catsStdMonadReaderForFunction1[HttpClient[F]] - ClientRunnable.lift(m.tailRecM(a)(fn.map(cr => cr.run(_)))) - } - - def pure[A](a: A): ClientRunnable[F, A] = PureRunnable(a) - } - - implicit def instances[F[_]] = new ClientRunnableMonad[F] - -} \ No newline at end of file +package masterleague4s +package net + +import spinoco.fs2.http +import http._ +import cats._ +import cats.implicits._ + +sealed trait ClientRunnable[F[_], +A] { + def run(client: HttpClient[F]): A +} + +case class PureRunnable[F[_], A](result: A) extends ClientRunnable[F, A] { + def run(client: HttpClient[F]) = result +} +case class LiftedRunnable[F[_], A](f: HttpClient[F] => A) extends ClientRunnable[F, A] { + def run(client: HttpClient[F]) = f(client) +} + +object ClientRunnable { + def lift[F[_], A](f: HttpClient[F] => A): ClientRunnable[F, A] = LiftedRunnable(f) + + def transform[F[_]]: (({ type l[a] = ClientRunnable[F, a] })#l ~> ({ type l[a] = Function1[HttpClient[F], a] })#l) = + new (({ type l[a] = ClientRunnable[F, a] })#l ~> ({ type l[a] = Function1[HttpClient[F], a] })#l) { + def apply[A](fa: ClientRunnable[F, A]): HttpClient[F] => A = fa.run _ + } + + class ClientRunnableMonad[F[_]] extends Monad[({ type l[a] = ClientRunnable[F, a] })#l] { + + def flatMap[A, B](fa: ClientRunnable[F, A])(f: A => ClientRunnable[F, B]): ClientRunnable[F, B] = { + ClientRunnable.lift((client: HttpClient[F]) => f(fa.run(client)).run(client)) + } + + def tailRecM[A, B](a: A)(fn: A => ClientRunnable[F, Either[A, B]]): ClientRunnable[F, B] = { + val m = cats.instances.all.catsStdMonadReaderForFunction1[HttpClient[F]] + ClientRunnable.lift(m.tailRecM(a)(fn.map(cr => cr.run(_)))) + } + + def pure[A](a: A): ClientRunnable[F, A] = PureRunnable(a) + } + + implicit def instances[F[_]] = new ClientRunnableMonad[F] + +} diff --git a/src/main/scala/net/Crawler.scala b/src/main/scala/net/Crawler.scala index 6a0a183..2433de8 100644 --- a/src/main/scala/net/Crawler.scala +++ b/src/main/scala/net/Crawler.scala @@ -6,7 +6,10 @@ import scala.language.higherKinds object Crawler { - def crawl[F[_], A, B](a: A, a2b: A => Stream[F, B], b2a: B => Stream[F, A], sleep: => Stream[F, Unit]): Stream[F, B] = { + def crawl[F[_], A, B](a: A, + a2b: A => Stream[F, B], + b2a: B => Stream[F, A], + sleep: => Stream[F, Unit]): Stream[F, B] = { def rec(as: Stream[F, A]): Stream[F, B] = as.flatMap { a => a2b(a).flatMap { b => @@ -15,4 +18,4 @@ object Crawler { } rec(Stream(a)) } -} \ No newline at end of file +} diff --git a/src/main/scala/net/Endpoints.scala b/src/main/scala/net/Endpoints.scala index 59a07c4..247b2bc 100644 --- a/src/main/scala/net/Endpoints.scala +++ b/src/main/scala/net/Endpoints.scala @@ -10,14 +10,16 @@ object Endpoints { val auth = Uri.parse("https://api.masterleague.net/auth/token/").require - val heroes = tag[IdHero][Uri](Uri.parse("https://api.masterleague.net/heroes/?format=json&page_size=25").require) - val matches = tag[IdMatch][Uri](Uri.parse("https://api.masterleague.net/matches/?format=json&page_size=25").require) + val heroes = tag[IdHero][Uri](Uri.parse("https://api.masterleague.net/heroes/?format=json&page_size=25").require) + val matches = tag[IdMatch][Uri](Uri.parse("https://api.masterleague.net/matches/?format=json&page_size=25").require) val battlegrounds = tag[IdBattleground][Uri](Uri.parse("https://api.masterleague.net/maps/?format=json").require) - val regions = tag[IdRegion][Uri](Uri.parse("https://api.masterleague.net/regions/?format=json").require) - val teams = tag[IdTeam][Uri](Uri.parse("https://api.masterleague.net/teams/?format=json&page_size=100").require) - val patches = tag[IdPatch][Uri](Uri.parse("https://api.masterleague.net/patches/?format=json").require) - val players = tag[IdPlayer][Uri](Uri.parse("https://api.masterleague.net/players/?format=json&page_size=100").require) - val tournaments = tag[IdTournament][Uri](Uri.parse("https://api.masterleague.net/tournaments/?format=json&page_size=100").require) + val regions = tag[IdRegion][Uri](Uri.parse("https://api.masterleague.net/regions/?format=json").require) + val teams = tag[IdTeam][Uri](Uri.parse("https://api.masterleague.net/teams/?format=json&page_size=100").require) + val patches = tag[IdPatch][Uri](Uri.parse("https://api.masterleague.net/patches/?format=json").require) + val players = + tag[IdPlayer][Uri](Uri.parse("https://api.masterleague.net/players/?format=json&page_size=100").require) + val tournaments = + tag[IdTournament][Uri](Uri.parse("https://api.masterleague.net/tournaments/?format=json&page_size=100").require) val calendar = tag[CalendarEntryId][Uri](Uri.parse("https://api.masterleague.net/calendar/?format=json").require) -} \ No newline at end of file +} diff --git a/src/main/scala/net/ErrorDetailed.scala b/src/main/scala/net/ErrorDetailed.scala index 34f0e9c..87f7cf2 100644 --- a/src/main/scala/net/ErrorDetailed.scala +++ b/src/main/scala/net/ErrorDetailed.scala @@ -1,25 +1,24 @@ -package masterleague4s -package net - -import scala.concurrent.duration.Duration -import scala.util.Try - -case class APIError(detail: String) - -//model specific errors as extractors. This can't be checked/enforced in a pattern match -//and I'm not sure it's a good idea - -case class Throttled(underlying: APIError, retryIn: Duration) -object Throttled { - def unapply(error: APIError): Option[Throttled] = { - val pattern = """\d+(\.\d+)? \w+""".r - for { - extracted <- pattern.findFirstIn(error.detail) - duration <- Try(Duration(extracted)).toOption - } yield Throttled(error, duration) - } -} - -//login error: -/*{"non_field_errors":["Unable to log in with provided credentials."]}*/ - +package masterleague4s +package net + +import scala.concurrent.duration.Duration +import scala.util.Try + +case class APIError(detail: String) + +//model specific errors as extractors. This can't be checked/enforced in a pattern match +//and I'm not sure it's a good idea + +case class Throttled(underlying: APIError, retryIn: Duration) +object Throttled { + def unapply(error: APIError): Option[Throttled] = { + val pattern = """\d+(\.\d+)? \w+""".r + for { + extracted <- pattern.findFirstIn(error.detail) + duration <- Try(Duration(extracted)).toOption + } yield Throttled(error, duration) + } +} + +//login error: +/*{"non_field_errors":["Unable to log in with provided credentials."]}*/ diff --git a/src/main/scala/net/Filters.scala b/src/main/scala/net/Filters.scala index 14220cc..1497257 100644 --- a/src/main/scala/net/Filters.scala +++ b/src/main/scala/net/Filters.scala @@ -10,23 +10,29 @@ case class MatchFilter(map: Option[Long], tournament: Option[Long], patch: Optio object MatchFilter { def empty = MatchFilter(None, None, None, None) implicit def querybuilder = new QueryBuilder[MatchFilter] { - def query(filter: MatchFilter) = Query(List("map" -> filter.map, "tournament" -> filter.tournament, "patch" -> filter.patch, "player" -> filter.player) - .collect { case (name, Some(id)) => (name, id.toString) }) + def query(filter: MatchFilter) = + Query( + List("map" -> filter.map, + "tournament" -> filter.tournament, + "patch" -> filter.patch, + "player" -> filter.player) + .collect { case (name, Some(id)) => (name, id.toString) }) } } case class HeroFilter(role: Option[Role]) object HeroFilter { - def empty = HeroFilter(None) + def empty = HeroFilter(None) def forRole(role: Role) = HeroFilter(Some(role)) implicit def querybuilder = new QueryBuilder[HeroFilter] { - def query(filter: HeroFilter) = Query(filter.role.toList.map { - case Warrior => "role" -> "1" - case Support => "role" -> "2" - case Assassin => "role" -> "3" - case Specialist => "role" -> "4" - }) + def query(filter: HeroFilter) = + Query(filter.role.toList.map { + case Warrior => "role" -> "1" + case Support => "role" -> "2" + case Assassin => "role" -> "3" + case Specialist => "role" -> "4" + }) } } @@ -53,21 +59,24 @@ object Filtering { def hbuilder = HeroFilter.querybuilder def tbuilder = TeamFilter.querybuilder - def filterMatches(base: Uri @@ IdMatch, filter: MatchFilter) = tag[IdMatch][Uri](base.withQuery(base.query |+| mbuilder.query(filter))) + def filterMatches(base: Uri @@ IdMatch, filter: MatchFilter) = + tag[IdMatch][Uri](base.withQuery(base.query |+| mbuilder.query(filter))) implicit class MatchFilterOps(receiver: Uri @@ IdMatch) { def filter(f: MatchFilter) = filterMatches(receiver, f) } - def filterHeroes(base: Uri @@ IdHero, filter: HeroFilter) = tag[IdHero][Uri](base.withQuery(base.query |+| hbuilder.query(filter))) + def filterHeroes(base: Uri @@ IdHero, filter: HeroFilter) = + tag[IdHero][Uri](base.withQuery(base.query |+| hbuilder.query(filter))) implicit class HeroFilterOps(receiver: Uri @@ IdHero) { def filter(f: HeroFilter) = filterHeroes(receiver, f) } - def filterTeams(base: Uri @@ IdTeam, filter: TeamFilter) = tag[IdTeam][Uri](base.withQuery(base.query |+| tbuilder.query(filter))) + def filterTeams(base: Uri @@ IdTeam, filter: TeamFilter) = + tag[IdTeam][Uri](base.withQuery(base.query |+| tbuilder.query(filter))) implicit class TeamFilterOps(receiver: Uri @@ IdTeam) { def filter(f: TeamFilter) = filterTeams(receiver, f) } -} \ No newline at end of file +} diff --git a/src/main/scala/net/QueryBuilder.scala b/src/main/scala/net/QueryBuilder.scala index 2b0e660..34b994e 100644 --- a/src/main/scala/net/QueryBuilder.scala +++ b/src/main/scala/net/QueryBuilder.scala @@ -4,6 +4,7 @@ package net import spinoco.protocol.http.Uri.Query import simulacrum._ -@typeclass trait QueryBuilder[A] { +@typeclass +trait QueryBuilder[A] { def query(a: A): Query -} \ No newline at end of file +} diff --git a/src/main/scala/net/Tokens.scala b/src/main/scala/net/Tokens.scala index b592ad2..6e2204a 100644 --- a/src/main/scala/net/Tokens.scala +++ b/src/main/scala/net/Tokens.scala @@ -23,16 +23,20 @@ object TokenAuthorization { val codec: Codec[TokenAuthorization] = (asciiConstant("Token") ~> (whitespace() ~> utf8String)).xmap( - { token => TokenAuthorization(Token(token)) }, _.credentials.token + { token => + TokenAuthorization(Token(token)) + }, + _.credentials.token ) //codec that first tries `Token sometoken` and then falls back to the known alternatives val customAuthorizationHeader: Codec[HttpHeader] = choice( - codec.upcast[HttpHeader], Authorization.codec.headerCodec + codec.upcast[HttpHeader], + Authorization.codec.headerCodec ) //codec for the full header, including header name val headerCodec: Codec[HttpHeader] = HttpHeaderCodec.codec(Int.MaxValue, ("Authorization" -> TokenAuthorization.customAuthorizationHeader)) -} \ No newline at end of file +} diff --git a/src/main/scala/net/UnfoldApiResult.scala b/src/main/scala/net/UnfoldApiResult.scala index 458eeeb..4ee6489 100644 --- a/src/main/scala/net/UnfoldApiResult.scala +++ b/src/main/scala/net/UnfoldApiResult.scala @@ -1,78 +1,89 @@ -package masterleague4s -package net - -import io.circe.Decoder -import fs2._ -import data._ -import matryoshka.data.Fix -import shapeless.tag.@@ -import spinoco.fs2.http._ -import spinoco.protocol.http.Uri -//import spinoco.protocol.http.header.Authorization -import data.Serialized._ -import codec.CirceSupport._ -import codec.FDecoders._ -import fs2.util.Catchable -import cats.implicits._ -import authorization.Token -//import spinoco.protocol.http.header.value.HttpCredentials.OAuth2BearerToken - -object UnfoldApiResult { - type StreamRunnable[F[_], A] = ClientRunnable[F, Stream[F, A]] - type RunnableApiStream[F[_], U, A] = StreamRunnable[F, APIResultF[A, U]] - - type RunnableResult[F[_], A] = Fix[({ type l[a] = RunnableApiStream[F, a, A] })#l] - - def unfoldApiResult[F[_]: Catchable, A: Decoder](uri: Uri @@ A, sleep: Stream[F, Unit], token: Option[Token]): RunnableResult[F, A] = { - implicit val bodyDecoder = circeDecoder[UriApiResult[A]](decodeAPICall) - - import spinoco.protocol.http.header.value.ContentType - import spinoco.protocol.http.header.value.MediaType - - def urimap(uriresult: UriApiResult[A]): APIResultF[A, RunnableResult[F, A]] = uriresult.bimap(id => id, uri => unfoldApiResult(uri, sleep, token)) - def streammap(str: Stream[F, UriApiResult[A]]): Stream[F, APIResultF[A, RunnableResult[F, A]]] = str.map(urimap) - - val r: HttpRequest[F] = token.foldLeft(HttpRequest.get[F](uri))((req, tok) => req.appendHeader(authorization.Auth.authheader(tok))) - - val one: HttpClient[F] => Stream[F, UriApiResult[A]] = (client: HttpClient[F]) => for { - response <- (sleep >> client.request(r)) - status = response.header.status - body <- { - if (status.isSuccess) Stream.eval(response.bodyAs[UriApiResult[A]]).map(_.require) - else { - val textbody = Stream.eval(response.withContentType(ContentType(MediaType.`text/plain`, None, None)).bodyAsString) - textbody.map(body => { - val error = s"Unexpected network response: status ${status.code} - ${status.longDescription} CONTENTS: $body" - throw new Exception(error) - }) - } - } - } yield body - - val lifted = ClientRunnable.lift(one) - type Unfix[X] = ClientRunnable[F, Stream[F, APIResultF[A, X]]] - - Fix[Unfix](ClientRunnable.instances.map(lifted)(streammap)) - } - - def linearizeApiResult[F[_]: Catchable, A: Decoder](uri: Uri @@ A, sleep: Stream[F, Unit], token: Option[Token]): ClientRunnable[F, Stream[F, APIResultF[A, Unit]]] = { - val init = unfoldApiResult(uri, sleep, token) - - val run = (client: HttpClient[F]) => { - - def rec(fix: RunnableResult[F, A]): Stream[F, APIResultF[A, Unit]] = { - val runnable = fix.unFix - val pages = runnable.run(client) - val these: Stream[F, APIResultF[A, Unit]] = pages.map(_.bimap(id => id, _ => ())) - //'t was nice knowing you, stack (TODO: Stack-safety(?)) - val those: Stream[F, APIResultF[A, Unit]] = pages.flatMap(page => page.next match { - case None => Stream.empty - case Some(n) => rec(n) - }) - these ++ those - } - rec(init) - } - ClientRunnable.lift(run) - } -} \ No newline at end of file +package masterleague4s +package net + +import io.circe.Decoder +import fs2._ +import data._ +import matryoshka.data.Fix +import shapeless.tag.@@ +import spinoco.fs2.http._ +import spinoco.protocol.http.Uri +//import spinoco.protocol.http.header.Authorization +import data.Serialized._ +import codec.CirceSupport._ +import codec.FDecoders._ +import fs2.util.Catchable +import cats.implicits._ +import authorization.Token +//import spinoco.protocol.http.header.value.HttpCredentials.OAuth2BearerToken + +object UnfoldApiResult { + type StreamRunnable[F[_], A] = ClientRunnable[F, Stream[F, A]] + type RunnableApiStream[F[_], U, A] = StreamRunnable[F, APIResultF[A, U]] + + type RunnableResult[F[_], A] = Fix[({ type l[a] = RunnableApiStream[F, a, A] })#l] + + def unfoldApiResult[F[_]: Catchable, A: Decoder](uri: Uri @@ A, + sleep: Stream[F, Unit], + token: Option[Token]): RunnableResult[F, A] = { + implicit val bodyDecoder = circeDecoder[UriApiResult[A]](decodeAPICall) + + import spinoco.protocol.http.header.value.ContentType + import spinoco.protocol.http.header.value.MediaType + + def urimap(uriresult: UriApiResult[A]): APIResultF[A, RunnableResult[F, A]] = + uriresult.bimap(id => id, uri => unfoldApiResult(uri, sleep, token)) + def streammap(str: Stream[F, UriApiResult[A]]): Stream[F, APIResultF[A, RunnableResult[F, A]]] = str.map(urimap) + + val r: HttpRequest[F] = + token.foldLeft(HttpRequest.get[F](uri))((req, tok) => req.appendHeader(authorization.Auth.authheader(tok))) + + val one: HttpClient[F] => Stream[F, UriApiResult[A]] = (client: HttpClient[F]) => + for { + response <- (sleep >> client.request(r)) + status = response.header.status + body <- { + if (status.isSuccess) Stream.eval(response.bodyAs[UriApiResult[A]]).map(_.require) + else { + val textbody = + Stream.eval(response.withContentType(ContentType(MediaType.`text/plain`, None, None)).bodyAsString) + textbody.map(body => { + val error = + s"Unexpected network response: status ${status.code} - ${status.longDescription} CONTENTS: $body" + throw new Exception(error) + }) + } + } + } yield body + + val lifted = ClientRunnable.lift(one) + type Unfix[X] = ClientRunnable[F, Stream[F, APIResultF[A, X]]] + + Fix[Unfix](ClientRunnable.instances.map(lifted)(streammap)) + } + + def linearizeApiResult[F[_]: Catchable, A: Decoder]( + uri: Uri @@ A, + sleep: Stream[F, Unit], + token: Option[Token]): ClientRunnable[F, Stream[F, APIResultF[A, Unit]]] = { + val init = unfoldApiResult(uri, sleep, token) + + val run = (client: HttpClient[F]) => { + + def rec(fix: RunnableResult[F, A]): Stream[F, APIResultF[A, Unit]] = { + val runnable = fix.unFix + val pages = runnable.run(client) + val these: Stream[F, APIResultF[A, Unit]] = pages.map(_.bimap(id => id, _ => ())) + //'t was nice knowing you, stack (TODO: Stack-safety(?)) + val those: Stream[F, APIResultF[A, Unit]] = pages.flatMap(page => + page.next match { + case None => Stream.empty + case Some(n) => rec(n) + }) + these ++ those + } + rec(init) + } + ClientRunnable.lift(run) + } +} diff --git a/src/main/scala/net/util/QueryInstances.scala b/src/main/scala/net/util/QueryInstances.scala index 1cbe323..0589f1d 100644 --- a/src/main/scala/net/util/QueryInstances.scala +++ b/src/main/scala/net/util/QueryInstances.scala @@ -1,28 +1,33 @@ -package masterleague4s -package net -package util - -import spinoco.protocol.http.Uri.Query -import cats._ - -object QueryInstances { - trait Instances extends Monoid[Query] with Show[Query] with Eq[Query] { - def empty: Query = Query.empty - def combine(x: Query, y: Query): Query = { - //only lawful for alphabetically ordered params by key with no duplicates - //this is not spec-enforced nor generally valid - //but it is for the use-case - val params = ((y.params ::: x.params).groupBy(_._1).toList.collect { case (_, (k, v) :: _) => (k, v) }).sortBy(_._1) - Query(params) - } - def show(q: Query) = q.params.map(pair => pair match { - case (k, "") => k - case (k, v) => s"$k=$v" - }).mkString("?", "&", "") - - def eqv(x: Query, y: Query) = x == y - - } - - implicit val instances: Instances = new Instances {} -} \ No newline at end of file +package masterleague4s +package net +package util + +import spinoco.protocol.http.Uri.Query +import cats._ + +object QueryInstances { + trait Instances extends Monoid[Query] with Show[Query] with Eq[Query] { + def empty: Query = Query.empty + def combine(x: Query, y: Query): Query = { + //only lawful for alphabetically ordered params by key with no duplicates + //this is not spec-enforced nor generally valid + //but it is for the use-case + val params = + ((y.params ::: x.params).groupBy(_._1).toList.collect { case (_, (k, v) :: _) => (k, v) }).sortBy(_._1) + Query(params) + } + def show(q: Query) = + q.params + .map(pair => + pair match { + case (k, "") => k + case (k, v) => s"$k=$v" + }) + .mkString("?", "&", "") + + def eqv(x: Query, y: Query) = x == y + + } + + implicit val instances: Instances = new Instances {} +} diff --git a/src/main/scala/stats/Stats.scala b/src/main/scala/stats/Stats.scala index f68ec1b..9cb3350 100644 --- a/src/main/scala/stats/Stats.scala +++ b/src/main/scala/stats/Stats.scala @@ -1,69 +1,78 @@ -package masterleague4s -package stats - -import masterleague4s.data._ - -case class HeroStats(picks: Int, wins: Int, bans: Int, firstpicks: Int, firstbans: Int, uncontested: Int) { - def contested = picks + bans - def total = contested + uncontested - def winRate = if (picks == 0) None else Some(wins.toDouble / picks) - def contestedRate = if (total == 0) None else Some(contested.toDouble / total) - def firstPickRate = if (total == 0) None else Some(firstpicks.toDouble / total) - def firstBanRate = if (total == 0) None else Some(firstbans.toDouble / total) - - def prettypct(d: Double): String = { - s"${(d * 100).toInt}%" - } - - def pctOrNa(o: Option[Double]): String = o.map(prettypct).getOrElse("N/A") - - def winLines: List[String] = if (picks == 0) List() - else if (wins == 0) List(s"No wins in $picks games") - else if (wins == picks) List(s"No losses in $picks games") - else List(s"$wins/$picks (${prettypct(winRate.get)}) winrate") - - def contestLines: List[String] = if (contested == 0) List(s"Uncontested so far for $uncontested matches") - else List(s"$picks picks and $bans bans in $total matches (${pctOrNa(contestedRate)} contested)") :+ { - if (firstpicks == 0 && firstbans == 0) "none of which as first pick or ban" - else s"with $firstpicks first picks (${pctOrNa(firstPickRate)}) and $firstbans first bans (${pctOrNa(firstBanRate)})" - } - - def pretty = contestLines ++ winLines -} -object HeroStats { - def empty = HeroStats(0, 0, 0, 0, 0, 0) -} - -object Stats { - - def forHeroInTourny(heroId: Long, tournyId: Long, matches: List[MatchF[_, Long, _, _, _, DraftF[Long, Long, PickF[Long, _]]]]): HeroStats = { - val matchesInTourny = matches.filter(m => m.tournament == tournyId) - heroStats(heroId, matchesInTourny) - } - - def forHeroInPatch(heroId: Long, patchId: Long, matches: List[MatchF[Long, _, _, _, _, DraftF[Long, Long, PickF[Long, _]]]]): HeroStats = { - val matchesInPatch = matches.filter(m => m.patch == patchId) - heroStats(heroId, matchesInPatch) - } - - def heroStats(heroId: Long, matches: List[MatchF[_, _, _, _, _, DraftF[Long, Long, PickF[Long, _]]]]): HeroStats = { - matches.foldLeft(HeroStats.empty)((stats, m) => { - val drafts = m.drafts - val (firstdraft :: seconddraft :: Nil) = m.drafts - val isfirstpick = firstdraft.picks.headOption.exists(p => p.hero == heroId) || seconddraft.picks.take(2).exists(p => p.hero == heroId) - val isfirstban = firstdraft.bans.headOption == Some(heroId) || seconddraft.bans.headOption == Some(heroId) - - val isPick = drafts.flatMap(_.picks).exists(p => p.hero == heroId) - val isBan = drafts.flatMap(_.bans).contains(heroId) - val isWin = drafts.filter(d => d.isWinner).flatMap(_.picks).exists(p => p.hero == heroId) - - val picks = stats.picks + { if (isPick) 1 else 0 } - val wins = stats.wins + { if (isWin) 1 else 0 } - val bans = stats.bans + { if (isBan) 1 else 0 } - val firstpicks = stats.firstpicks + { if (isfirstpick) 1 else 0 } - val firstbans = stats.firstbans + { if (isfirstban) 1 else 0 } - val uncontested = stats.uncontested + { if (isPick || isBan) 0 else 1 } - HeroStats(picks, wins, bans, firstpicks, firstbans, uncontested) - }) - } -} \ No newline at end of file +package masterleague4s +package stats + +import masterleague4s.data._ + +case class HeroStats(picks: Int, wins: Int, bans: Int, firstpicks: Int, firstbans: Int, uncontested: Int) { + def contested = picks + bans + def total = contested + uncontested + def winRate = if (picks == 0) None else Some(wins.toDouble / picks) + def contestedRate = if (total == 0) None else Some(contested.toDouble / total) + def firstPickRate = if (total == 0) None else Some(firstpicks.toDouble / total) + def firstBanRate = if (total == 0) None else Some(firstbans.toDouble / total) + + def prettypct(d: Double): String = { + s"${(d * 100).toInt}%" + } + + def pctOrNa(o: Option[Double]): String = o.map(prettypct).getOrElse("N/A") + + def winLines: List[String] = + if (picks == 0) List() + else if (wins == 0) List(s"No wins in $picks games") + else if (wins == picks) List(s"No losses in $picks games") + else List(s"$wins/$picks (${prettypct(winRate.get)}) winrate") + + def contestLines: List[String] = + if (contested == 0) List(s"Uncontested so far for $uncontested matches") + else + List(s"$picks picks and $bans bans in $total matches (${pctOrNa(contestedRate)} contested)") :+ { + if (firstpicks == 0 && firstbans == 0) "none of which as first pick or ban" + else + s"with $firstpicks first picks (${pctOrNa(firstPickRate)}) and $firstbans first bans (${pctOrNa(firstBanRate)})" + } + + def pretty = contestLines ++ winLines +} +object HeroStats { + def empty = HeroStats(0, 0, 0, 0, 0, 0) +} + +object Stats { + + def forHeroInTourny(heroId: Long, + tournyId: Long, + matches: List[MatchF[_, Long, _, _, _, DraftF[Long, Long, PickF[Long, _]]]]): HeroStats = { + val matchesInTourny = matches.filter(m => m.tournament == tournyId) + heroStats(heroId, matchesInTourny) + } + + def forHeroInPatch(heroId: Long, + patchId: Long, + matches: List[MatchF[Long, _, _, _, _, DraftF[Long, Long, PickF[Long, _]]]]): HeroStats = { + val matchesInPatch = matches.filter(m => m.patch == patchId) + heroStats(heroId, matchesInPatch) + } + + def heroStats(heroId: Long, matches: List[MatchF[_, _, _, _, _, DraftF[Long, Long, PickF[Long, _]]]]): HeroStats = { + matches.foldLeft(HeroStats.empty)((stats, m) => { + val drafts = m.drafts + val (firstdraft :: seconddraft :: Nil) = m.drafts + val isfirstpick = firstdraft.picks.headOption + .exists(p => p.hero == heroId) || seconddraft.picks.take(2).exists(p => p.hero == heroId) + val isfirstban = firstdraft.bans.headOption == Some(heroId) || seconddraft.bans.headOption == Some(heroId) + + val isPick = drafts.flatMap(_.picks).exists(p => p.hero == heroId) + val isBan = drafts.flatMap(_.bans).contains(heroId) + val isWin = drafts.filter(d => d.isWinner).flatMap(_.picks).exists(p => p.hero == heroId) + + val picks = stats.picks + { if (isPick) 1 else 0 } + val wins = stats.wins + { if (isWin) 1 else 0 } + val bans = stats.bans + { if (isBan) 1 else 0 } + val firstpicks = stats.firstpicks + { if (isfirstpick) 1 else 0 } + val firstbans = stats.firstbans + { if (isfirstban) 1 else 0 } + val uncontested = stats.uncontested + { if (isPick || isBan) 0 else 1 } + HeroStats(picks, wins, bans, firstpicks, firstbans, uncontested) + }) + } +} diff --git a/src/test/scala/HeroDecoderSpec.scala b/src/test/scala/HeroDecoderSpec.scala index 1c76df6..0467b37 100644 --- a/src/test/scala/HeroDecoderSpec.scala +++ b/src/test/scala/HeroDecoderSpec.scala @@ -14,7 +14,8 @@ class HeroDecoderSpec extends Specification { parse Abathur $parseAbathur """ - def abathurString = """{ + def abathurString = + """{ "id": 37, "name": "Abathur", "role": 4, @@ -59,4 +60,4 @@ case class MapEntry(id: Long, name: String, url: Uri) extends PlainEntry case class RegionEntry(id: Long, name: String) extends PlainEntry case class PatchEntry(id: Long, name: String, from_date: LocalDate, to_date: LocalDate) extends PlainEntry //"2017-05-15" -*/ \ No newline at end of file + */ diff --git a/src/test/scala/TeamResultDecoderSpec.scala b/src/test/scala/TeamResultDecoderSpec.scala index a7cae05..86c3469 100644 --- a/src/test/scala/TeamResultDecoderSpec.scala +++ b/src/test/scala/TeamResultDecoderSpec.scala @@ -246,4 +246,4 @@ class TeamResultDecoderSpec extends Specification { parseResult.isRight must beTrue } -} \ No newline at end of file +} diff --git a/src/test/scala/TournamentResultDecoder.scala b/src/test/scala/TournamentResultDecoder.scala index 9b10bdf..1071049 100644 --- a/src/test/scala/TournamentResultDecoder.scala +++ b/src/test/scala/TournamentResultDecoder.scala @@ -610,4 +610,4 @@ class TournamentResultDecoderSpec extends Specification { parseResult.isRight must beTrue } -} \ No newline at end of file +} diff --git a/src/test/scala/codecs/ExampleSpec.scala b/src/test/scala/codecs/ExampleSpec.scala index 6a03bb0..ae93e26 100644 --- a/src/test/scala/codecs/ExampleSpec.scala +++ b/src/test/scala/codecs/ExampleSpec.scala @@ -11,10 +11,11 @@ class ExampleSpec extends Specification with org.specs2.ScalaCheck { a more complex property $ex2 """ - def abStringGen = for { - ab <- Gen.oneOf("a", "b") - ab2 <- Gen.oneOf("a", "b") - } yield (ab + ab2) + def abStringGen = + for { + ab <- Gen.oneOf("a", "b") + ab2 <- Gen.oneOf("a", "b") + } yield (ab + ab2) implicit def abStrings: Arbitrary[String] = Arbitrary(abStringGen) @@ -22,7 +23,9 @@ class ExampleSpec extends Specification with org.specs2.ScalaCheck { def ex1 = prop((s: String) => s must contain("a") or contain("b")).setArbitrary(abStrings) // use the setArbitrary method for the nth argument - def ex2 = prop((s1: String, s2: String) => (s1 + s2) must contain("a") or contain("b")). - setArbitrary1(abStrings).setArbitrary2(abStrings) + def ex2 = + prop((s1: String, s2: String) => (s1 + s2) must contain("a") or contain("b")) + .setArbitrary1(abStrings) + .setArbitrary2(abStrings) -} \ No newline at end of file +} diff --git a/src/test/scala/codecs/RoundtripSpec.scala b/src/test/scala/codecs/RoundtripSpec.scala index 3bd699c..acf9f64 100644 --- a/src/test/scala/codecs/RoundtripSpec.scala +++ b/src/test/scala/codecs/RoundtripSpec.scala @@ -18,13 +18,14 @@ class RoundtripSpec extends Specification with org.specs2.ScalaCheck { import FDecoders._ - def fromUri = prop((u: Uri) => { - val reparsed = u.asJson.as[Uri] - reparsed match { - case Left(_) => 1 must_== 1 - case Right(uri) => uri must_== u - } - }) + def fromUri = + prop((u: Uri) => { + val reparsed = u.asJson.as[Uri] + reparsed match { + case Left(_) => 1 must_== 1 + case Right(uri) => uri must_== u + } + }) import masterleague4s.net.APIError @@ -34,12 +35,13 @@ class RoundtripSpec extends Specification with org.specs2.ScalaCheck { def token = roundtrip[Token] - def roundtrip[A: Arbitrary: Decoder: Encoder] = prop((a: A) => { - val reparsed = a.asJson.as[A] - reparsed match { - case Left(err) => err must_== false - case Right(parsed) => parsed must_== a - } - }) + def roundtrip[A: Arbitrary: Decoder: Encoder] = + prop((a: A) => { + val reparsed = a.asJson.as[A] + reparsed match { + case Left(err) => err must_== false + case Right(parsed) => parsed must_== a + } + }) -} \ No newline at end of file +} diff --git a/src/test/scala/discipline/DisciplineSpec.scala b/src/test/scala/discipline/DisciplineSpec.scala index 6724be3..73c0849 100644 --- a/src/test/scala/discipline/DisciplineSpec.scala +++ b/src/test/scala/discipline/DisciplineSpec.scala @@ -1,10 +1,10 @@ -package masterleague4s -package instances - -import org.specs2.Specification -import org.typelevel.discipline.specs2.Discipline -import org.scalacheck.Arbitrary - -trait DisciplineSpec extends Specification with Discipline { - implicit def arbAndOnly[A](implicit a: A): Arbitrary[A] = Arbitrary(a) -} \ No newline at end of file +package masterleague4s +package instances + +import org.specs2.Specification +import org.typelevel.discipline.specs2.Discipline +import org.scalacheck.Arbitrary + +trait DisciplineSpec extends Specification with Discipline { + implicit def arbAndOnly[A](implicit a: A): Arbitrary[A] = Arbitrary(a) +} diff --git a/src/test/scala/discipline/EitherExampleSpec.scala b/src/test/scala/discipline/EitherExampleSpec.scala index 5837254..4154cc6 100644 --- a/src/test/scala/discipline/EitherExampleSpec.scala +++ b/src/test/scala/discipline/EitherExampleSpec.scala @@ -1,13 +1,13 @@ -package masterleague4s -package instances - -import cats.implicits._ -import cats.laws.discipline.FunctorTests - -class EitherExampleSpec extends DisciplineSpec { - def is = s2""" - Either[Int, ?] forms a functor $e1 - """ - - def e1 = checkAll("Either[Int, Int]", FunctorTests[({ type l[a] = Either[Int, a] })#l].functor[Int, Int, Int]) -} \ No newline at end of file +package masterleague4s +package instances + +import cats.implicits._ +import cats.laws.discipline.FunctorTests + +class EitherExampleSpec extends DisciplineSpec { + def is = s2""" + Either[Int, ?] forms a functor $e1 + """ + + def e1 = checkAll("Either[Int, Int]", FunctorTests[({ type l[a] = Either[Int, a] })#l].functor[Int, Int, Int]) +} diff --git a/src/test/scala/discipline/data/Generators.scala b/src/test/scala/discipline/data/Generators.scala index 939a457..536b54b 100644 --- a/src/test/scala/discipline/data/Generators.scala +++ b/src/test/scala/discipline/data/Generators.scala @@ -1,65 +1,69 @@ -package masterleague4s -package instances - -import org.scalacheck.Arbitrary -import org.scalacheck.Arbitrary._ -import org.scalacheck.Gen -import masterleague4s.data._ -import spinoco.protocol.http._ -import spinoco.protocol.http.Uri._ -//import org.scalacheck.ScalacheckShapeless._ - -object Generators { - - implicit val arbScheme: Arbitrary[HttpScheme.Value] = Arbitrary(Gen.oneOf(HttpScheme.HTTP, HttpScheme.HTTPS, HttpScheme.WS, HttpScheme.WSS)) - - implicit val arbHP: Arbitrary[HostPort] = Arbitrary(for { - host <- arbitrary[String] - port <- arbitrary[Option[Int]] - } yield HostPort(host, port)) - - implicit val arbPath: Arbitrary[Path] = Arbitrary(for { - init <- arbitrary[Boolean] - trailing <- arbitrary[Boolean] - segments <- arbitrary[List[String]] - } yield Path(init, trailing, segments)) - - implicit val arbQuery: Arbitrary[Query] = Arbitrary(for { - params <- arbitrary[List[(String, String)]] - //allows only alphabetically ordered params by key with no duplicate - //this is not spec-enforced nor generally valid - //but it is for the use-case - } yield Query((params.groupBy(_._1).toList.collect { case (_, (k, v) :: _) => (k, v) }).sortBy(_._1))) - - implicit val arbUri: Arbitrary[Uri] = Arbitrary(for { - scheme <- arbitrary[HttpScheme.Value] - host <- arbitrary[HostPort] - path <- arbitrary[Path] - query <- arbitrary[Query] - } yield Uri(scheme, host, path, query)) - - implicit def arbPortrait: Arbitrary[HeroPortrait] = Arbitrary(for { - small <- arbitrary[Uri] - medium <- arbitrary[Uri] - } yield HeroPortrait(small, medium)) - - implicit def arbherof[A: Arbitrary]: Arbitrary[HeroF[A]] = Arbitrary(for { - name <- arbitrary[String] - role <- arbitrary[A] - url <- arbitrary[Uri] - portrait <- arbitrary[HeroPortrait] - } yield HeroF(name, role, url, portrait)) - - import masterleague4s.net.APIError - - implicit def arbThrottled: Arbitrary[APIError] = Arbitrary(for { - cause <- arbitrary[String] - } yield APIError(cause)) - - import masterleague4s.net.authorization.Token - implicit def arbToken: Arbitrary[Token] = Arbitrary(for { - value <- arbitrary[String] - } yield Token(value)) - -} - +package masterleague4s +package instances + +import org.scalacheck.Arbitrary +import org.scalacheck.Arbitrary._ +import org.scalacheck.Gen +import masterleague4s.data._ +import spinoco.protocol.http._ +import spinoco.protocol.http.Uri._ +//import org.scalacheck.ScalacheckShapeless._ + +object Generators { + + implicit val arbScheme: Arbitrary[HttpScheme.Value] = Arbitrary( + Gen.oneOf(HttpScheme.HTTP, HttpScheme.HTTPS, HttpScheme.WS, HttpScheme.WSS)) + + implicit val arbHP: Arbitrary[HostPort] = Arbitrary(for { + host <- arbitrary[String] + port <- arbitrary[Option[Int]] + } yield HostPort(host, port)) + + implicit val arbPath: Arbitrary[Path] = Arbitrary(for { + init <- arbitrary[Boolean] + trailing <- arbitrary[Boolean] + segments <- arbitrary[List[String]] + } yield Path(init, trailing, segments)) + + implicit val arbQuery: Arbitrary[Query] = Arbitrary(for { + params <- arbitrary[List[(String, String)]] + //allows only alphabetically ordered params by key with no duplicate + //this is not spec-enforced nor generally valid + //but it is for the use-case + } yield Query((params.groupBy(_._1).toList.collect { case (_, (k, v) :: _) => (k, v) }).sortBy(_._1))) + + implicit val arbUri: Arbitrary[Uri] = Arbitrary(for { + scheme <- arbitrary[HttpScheme.Value] + host <- arbitrary[HostPort] + path <- arbitrary[Path] + query <- arbitrary[Query] + } yield Uri(scheme, host, path, query)) + + implicit def arbPortrait: Arbitrary[HeroPortrait] = + Arbitrary(for { + small <- arbitrary[Uri] + medium <- arbitrary[Uri] + } yield HeroPortrait(small, medium)) + + implicit def arbherof[A: Arbitrary]: Arbitrary[HeroF[A]] = + Arbitrary(for { + name <- arbitrary[String] + role <- arbitrary[A] + url <- arbitrary[Uri] + portrait <- arbitrary[HeroPortrait] + } yield HeroF(name, role, url, portrait)) + + import masterleague4s.net.APIError + + implicit def arbThrottled: Arbitrary[APIError] = + Arbitrary(for { + cause <- arbitrary[String] + } yield APIError(cause)) + + import masterleague4s.net.authorization.Token + implicit def arbToken: Arbitrary[Token] = + Arbitrary(for { + value <- arbitrary[String] + } yield Token(value)) + +} diff --git a/src/test/scala/discipline/data/HeroFInstancesSpec.scala b/src/test/scala/discipline/data/HeroFInstancesSpec.scala index 4be81a3..3fb7cc6 100644 --- a/src/test/scala/discipline/data/HeroFInstancesSpec.scala +++ b/src/test/scala/discipline/data/HeroFInstancesSpec.scala @@ -1,17 +1,17 @@ -package masterleague4s -package instances -package data - -import cats.laws.discipline.TraverseTests -import masterleague4s.data.HeroF -import masterleague4s.data.HeroF._ -import Generators._ -import cats.implicits._ - -class HeroFInstancesSpec extends DisciplineSpec { - def is = s2""" - HeroF[Int] forms a traversable functor $e1 - """ - - def e1 = checkAll("HeroF[Int]", TraverseTests[HeroF].traverse[Int, Long, Byte, Short, List, Vector]) -} \ No newline at end of file +package masterleague4s +package instances +package data + +import cats.laws.discipline.TraverseTests +import masterleague4s.data.HeroF +import masterleague4s.data.HeroF._ +import Generators._ +import cats.implicits._ + +class HeroFInstancesSpec extends DisciplineSpec { + def is = s2""" + HeroF[Int] forms a traversable functor $e1 + """ + + def e1 = checkAll("HeroF[Int]", TraverseTests[HeroF].traverse[Int, Long, Byte, Short, List, Vector]) +} diff --git a/src/test/scala/discipline/net/util/QueryInstanceSpec.scala b/src/test/scala/discipline/net/util/QueryInstanceSpec.scala index b74a62d..507fccf 100644 --- a/src/test/scala/discipline/net/util/QueryInstanceSpec.scala +++ b/src/test/scala/discipline/net/util/QueryInstanceSpec.scala @@ -1,17 +1,17 @@ -package masterleague4s -package instances -package net -package util - -import cats.kernel.laws.GroupLaws -import spinoco.protocol.http.Uri.Query -import masterleague4s.instances.Generators._ -import masterleague4s.net.util.QueryInstances._ - -class QueryInstancesSpec extends DisciplineSpec { - def is = s2""" - Query forms a monoid $e1 - """ - - def e1 = checkAll("Query", GroupLaws[Query].monoid) -} \ No newline at end of file +package masterleague4s +package instances +package net +package util + +import cats.kernel.laws.GroupLaws +import spinoco.protocol.http.Uri.Query +import masterleague4s.instances.Generators._ +import masterleague4s.net.util.QueryInstances._ + +class QueryInstancesSpec extends DisciplineSpec { + def is = s2""" + Query forms a monoid $e1 + """ + + def e1 = checkAll("Query", GroupLaws[Query].monoid) +} diff --git a/src/test/scala/net/EndpointsSpec.scala b/src/test/scala/net/EndpointsSpec.scala index f5ac715..dc9e64a 100644 --- a/src/test/scala/net/EndpointsSpec.scala +++ b/src/test/scala/net/EndpointsSpec.scala @@ -21,15 +21,15 @@ class EndpointsSpec extends Specification { import Endpoints._ import spinoco.protocol.http.HttpScheme._ - def shost = host.scheme === HTTPS - def sauth = auth.scheme === HTTPS - def smatches = matches.scheme === HTTPS + def shost = host.scheme === HTTPS + def sauth = auth.scheme === HTTPS + def smatches = matches.scheme === HTTPS def sbattlegrounds = battlegrounds.scheme === HTTPS - def sregions = regions.scheme === HTTPS - def steams = teams.scheme === HTTPS - def spatches = patches.scheme === HTTPS - def splayers = players.scheme === HTTPS - def stournaments = tournaments.scheme === HTTPS - def scalendar = calendar.scheme === HTTPS + def sregions = regions.scheme === HTTPS + def steams = teams.scheme === HTTPS + def spatches = patches.scheme === HTTPS + def splayers = players.scheme === HTTPS + def stournaments = tournaments.scheme === HTTPS + def scalendar = calendar.scheme === HTTPS -} \ No newline at end of file +} diff --git a/src/test/scala/stats/HeroStatsSpec.scala b/src/test/scala/stats/HeroStatsSpec.scala index 19432c9..6edf7d2 100644 --- a/src/test/scala/stats/HeroStatsSpec.scala +++ b/src/test/scala/stats/HeroStatsSpec.scala @@ -14,7 +14,7 @@ class HeroStatsSpec extends Specification with org.specs2.ScalaCheck { def contestedRate = if (total == 0) None else Some(contested.toDouble / total) def firstPickRate = if (total == 0) None else Some(firstpicks.toDouble / total) def firstBanRate = if (total == 0) None else Some(firstbans.toDouble / total) - */ + */ def is = s2""" contested >= picks $contestedpicks @@ -25,28 +25,34 @@ class HeroStatsSpec extends Specification with org.specs2.ScalaCheck { firstbanrate <= contested $firstbanban """ - def contestedpicks = prop((hstats: HeroStats) => { - hstats.contested must be >= hstats.picks - }) - - def contestedbans = prop((hstats: HeroStats) => { - hstats.contested must be >= hstats.bans - }) - - def totalcontested = prop((hstats: HeroStats) => { - hstats.total must be >= hstats.contested - }) - - def totaluncontested = prop((hstats: HeroStats) => { - hstats.total must be >= hstats.uncontested - }) - - def firstpickpick = prop((hstats: HeroStats) => { - hstats.firstPickRate must be <= hstats.contestedRate - }) - - def firstbanban = prop((hstats: HeroStats) => { - hstats.firstBanRate must be <= hstats.contestedRate - }) - -} \ No newline at end of file + def contestedpicks = + prop((hstats: HeroStats) => { + hstats.contested must be >= hstats.picks + }) + + def contestedbans = + prop((hstats: HeroStats) => { + hstats.contested must be >= hstats.bans + }) + + def totalcontested = + prop((hstats: HeroStats) => { + hstats.total must be >= hstats.contested + }) + + def totaluncontested = + prop((hstats: HeroStats) => { + hstats.total must be >= hstats.uncontested + }) + + def firstpickpick = + prop((hstats: HeroStats) => { + hstats.firstPickRate must be <= hstats.contestedRate + }) + + def firstbanban = + prop((hstats: HeroStats) => { + hstats.firstBanRate must be <= hstats.contestedRate + }) + +} diff --git a/src/test/scala/stats/StatsGenerators.scala b/src/test/scala/stats/StatsGenerators.scala index 5b7d6f2..eddeffb 100644 --- a/src/test/scala/stats/StatsGenerators.scala +++ b/src/test/scala/stats/StatsGenerators.scala @@ -19,4 +19,3 @@ object StatsGenerators { } } -