Skip to content

Commit

Permalink
Requests implementation + Add implicit instance for v2_akka lookupuse…
Browse files Browse the repository at this point in the history
…r HttpEndpoint
  • Loading branch information
MiguelGomezC committed May 31, 2022
1 parent 1a6cdd7 commit f6009b7
Show file tree
Hide file tree
Showing 17 changed files with 289 additions and 28 deletions.
7 changes: 3 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,12 @@ src_managed/
project/boot/
project/plugins/project/

desktop.ini

# Scala-IDE specific
.scala_dependencies
.worksheet
*.sc

##
## ECLIPSE-Specific.
Expand Down Expand Up @@ -125,8 +127,7 @@ node_modules
*.py[co]
__pycache__
*.egg-info
*~
*.bak

.ipynb_checkpoints
.tox
.DS_Store
Expand All @@ -135,9 +136,7 @@ __pycache__
.coverage
.pytest_cache

*.swp
*.map
.idea/
Read the Docs
config.rst

Expand Down
4 changes: 3 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ val AkkaVersion = "2.6.8"
val AkkaHttpVersion = "10.2.4"

libraryDependencies ++= Seq(

"com.github.alexarchambault" %% "case-app" % "2.0.1",
"com.github.pureconfig" %% "pureconfig" % "0.14.0",
"ch.qos.logback" % "logback-classic" % "1.2.3",
Expand All @@ -17,7 +18,8 @@ libraryDependencies ++= Seq(
"com.typesafe.akka" %% "akka-slf4j" % AkkaVersion,
"com.typesafe.akka" %% "akka-actor-typed" % AkkaVersion,
"com.typesafe.akka" %% "akka-stream" % AkkaVersion,
"com.typesafe.akka" %% "akka-http" % AkkaHttpVersion)
"com.typesafe.akka" %% "akka-http" % AkkaHttpVersion,
"com.lihaoyi" %% "requests" % "0.7.0")

testFrameworks += new TestFramework("utest.runner.Framework")

Expand Down
3 changes: 2 additions & 1 deletion src/main/scala/dev/habla/twitter/main/Command.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package dev.habla.twitter
package v2
package main

import v2._

sealed abstract class Command

case class LookupTweet(
Expand Down
44 changes: 22 additions & 22 deletions src/main/scala/dev/habla/twitter/main/Main.scala
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
package dev.habla.twitter
package v2
package main

import v2._

import scala.concurrent.{ExecutionContext, ExecutionContextExecutor, Future}
import _root_.akka.actor.typed.ActorSystem
import _root_.akka.actor.typed.scaladsl.Behaviors
import caseapp._

import scala.util.Success
import scala.util.Failure

object Main extends CommandApp[Command]{

import scala.util.{Failure, Success}

object Main extends CommandApp[Command] {


def run(command: Command, rargs: RemainingArgs): Unit =
Expand All @@ -23,20 +21,23 @@ object Main extends CommandApp[Command]{
case cmd: LookupUsers => runLookupUsers(cmd)
}

def runLookupTweet(cmd: LookupTweet): Unit = withExecutionContext{
implicit system => implicit ec =>
v2_akka.lookupt.Run(cmd.toLookupTweetRequest)
def runLookupTweet(cmd: LookupTweet): Unit = withExecutionContext {
implicit system =>
implicit ec =>
v2_akka.lookupt.Run(cmd.toLookupTweetRequest)
}(println, _.printStackTrace)

def runSearchRecent(search: SearchRecent): Unit = withExecutionContext{
implicit system => implicit ec => search.toSearchRecentCommand match {
case Right(singleRequest: recents.SingleRequest) =>
v2_akka.recents.Run(singleRequest)
case Right(pagination: recents.Pagination) =>
v2_akka.recents.RunPagination(pagination)
case Left(error) =>
Future.failed(new Exception(error))
}
def runSearchRecent(search: SearchRecent): Unit = withExecutionContext {
implicit system =>
implicit ec =>
search.toSearchRecentCommand match {
case Right(singleRequest: recents.SingleRequest) =>
v2_akka.recents.Run(singleRequest)
case Right(pagination: recents.Pagination) =>
v2_akka.recents.RunPagination(pagination)
case Left(error) =>
Future.failed(new Exception(error))
}
}(println, _.printStackTrace)

def runLookupUser(cmd: LookupUser): Unit = withExecutionContext {
Expand All @@ -52,9 +53,9 @@ object Main extends CommandApp[Command]{
}(println, _.printStackTrace)

def withExecutionContext[A](
run: ActorSystem[_] => ExecutionContext => Future[A])(
onSuccess: A => Unit,
onFailure: Throwable => Unit
run: ActorSystem[_] => ExecutionContext => Future[A])(
onSuccess: A => Unit,
onFailure: Throwable => Unit
): Unit = {
val system = ActorSystem(Behaviors.empty, "TwitterV2")
implicit val ec: ExecutionContextExecutor = system.executionContext
Expand All @@ -64,4 +65,3 @@ object Main extends CommandApp[Command]{
}
}
}

26 changes: 26 additions & 0 deletions src/main/scala/dev/habla/twitter/main/MainLi.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package dev.habla.twitter
package main

import caseapp._

import scala.util.Try

object MainLi extends CommandApp[Command] {


def run(command: Command, rargs: RemainingArgs): Unit =
command match {
case cmd: LookupTweet => runLookupTweet(cmd)
case cmd: LookupUser => runLookupUser(cmd)
}

def runLookupTweet(cmd: LookupTweet): Unit =
Try(v2_requests.lookupt.Run(cmd.toLookupTweetRequest))
.fold(_.getMessage, println)

def runLookupUser(cmd: LookupUser): Unit = {
cmd.toLookupUserRequest.left.map(new Exception(_)).toTry
.flatMap(req => Try(v2_requests.lookupuser.Run(req))
.map(identity)).fold(_.getMessage, println)
}
}
3 changes: 3 additions & 0 deletions src/main/scala/dev/habla/twitter/v2_akka/HttpEndpoint.scala
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,8 @@ trait HttpEndpointSyntax{
trait HttpEndpointInstances{
implicit val lookuptEndpoint: HttpEndpoint.Aux[v2.lookupt.Request, v2.lookupt.Response] =
v2_akka.lookupt.Run

implicit val lookupuserEndpoint: HttpEndpoint.Aux[v2.lookupuser.Request, v2.lookupuser.Response] =
v2_akka.lookupuser.Run
}

15 changes: 15 additions & 0 deletions src/main/scala/dev/habla/twitter/v2_requests/HttpBody.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package dev.habla.twitter
package v2_requests

import spray.json._
import scala.util.Try

trait HttpBody{

def parseBody(response: requests.Response): Either[String, JsValue] = {
parseJson(response.text())
}

def parseJson(body: String): Either[String, JsValue] =
Try(body.parseJson).toEither.left.map(_ => body)
}
45 changes: 45 additions & 0 deletions src/main/scala/dev/habla/twitter/v2_requests/HttpEndpoint.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package dev.habla.twitter
package v2_requests


import requests.RequestBlob
import v2._

trait HttpEndpoint[Request]{
/* abstract interface */

type Response

def from(response: requests.Response): Response

def to(request: Request): requests.Request

/* concrete interface */

def apply(request: Request): Response =
from{
requests.get
.apply(to(request), RequestBlob.EmptyRequestBlob, requests.chunkedUpload)
}
}

object HttpEndpoint{
type Aux[Req, Res] = HttpEndpoint[Req]{type Response = Res }
}

trait HttpEndpointSyntax{

implicit class HttpEndpointRequestOps[Req, Res](request: Req)(implicit ep: HttpEndpoint.Aux[Req, Res]){
def single: Res =
ep.apply(request)
}
}

trait HttpEndpointInstances{
implicit val lookuptEndpoint: HttpEndpoint.Aux[lookupt.Request, lookupt.Response] =
v2_requests.lookupt.Run

implicit val lookupuserEndpoint: HttpEndpoint.Aux[lookupuser.Request, lookupuser.Response] =
v2_requests.lookupuser.Run
}

12 changes: 12 additions & 0 deletions src/main/scala/dev/habla/twitter/v2_requests/QueryParams.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package dev.habla.twitter
package v2_requests

trait QueryParams{

implicit class Params(params: Map[String, String]){
def add(name: String, value: Option[String]): Map[String, String] =
value.fold(params){ v => params + ((name, v)) }
def add(name: String, value: String): Map[String, String] =
add(name, Some(value))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package dev.habla.twitter
package v2_requests

import scala.util.Try

trait RateLimitHeaders {
def parseRateLimitHeaders(response: requests.Response): Option[(Int, Long)] = {
for {
rateResetH <- response.headers("x-rate-limit-reset").headOption
rateReset <- Try(rateResetH.toLong).toOption
rateRemainingH <- response.headers("x-rate-limit-remaining").headOption
rateRemaining <- Try(rateRemainingH.toInt).toOption
} yield (rateRemaining, rateReset)
}
}
38 changes: 38 additions & 0 deletions src/main/scala/dev/habla/twitter/v2_requests/lookupt/From.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package dev.habla.twitter
package v2_requests.lookupt

import v2_requests._
import v2.lookupt._
import spray.json._

import scala.util.Try


trait From extends HttpBody with RateLimitHeaders{

def from(response: requests.Response): Response = {
val bodyE = parseBody(response)
parseTweetInfo(response, bodyE)
.orElse(parseRateLimitExceeded(response))
.orElse(parseErroneousTextResponse(bodyE))
.orElse(parseErroneousJsonResponse(bodyE))
.getOrElse(ErroneousTextResponse("Not a lookup response"))
}

def parseTweetInfo(response: requests.Response, bodyE: Either[String,JsValue]): Option[TweetInfo] =
for {
body <- bodyE.toOption if response.statusCode == 200
tweets <- Try(body.convertTo[TweetInfo.Body]).toOption
(rateRemaining, rateReset) <- parseRateLimitHeaders(response)
} yield TweetInfo(tweets, rateRemaining, rateReset)

def parseRateLimitExceeded(response: requests.Response): Option[RateLimitExceeded] =
if (response.statusCode != 429) None
else parseRateLimitHeaders(response).map{ case (_, l) => RateLimitExceeded(l) }

def parseErroneousTextResponse(body: Either[String, JsValue]): Option[ErroneousJsonResponse] =
body.toOption.map(ErroneousJsonResponse)

def parseErroneousJsonResponse(body: Either[String, JsValue]): Option[ErroneousTextResponse] =
body.swap.toOption.map(ErroneousTextResponse)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package dev.habla.twitter
package v2_requests
package lookupt

object Run extends HttpEndpoint[v2.lookupt.Request]
with From
with To{
type Response = v2.lookupt.Response
}
19 changes: 19 additions & 0 deletions src/main/scala/dev/habla/twitter/v2_requests/lookupt/To.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package dev.habla.twitter
package v2_requests.lookupt

import v2.lookupt.Request
import v2_requests.QueryParams

trait To extends QueryParams{

def to(request: Request): requests.Request = {
requests.Request(
url = s"https://api.twitter.com/2/tweets/${request.id}",
params = Map[String, String]()
.add("expansions", request.expansions)
.add("tweet.fields", request.tweetFields)
.add("place.fields", request.placeFields),
headers = Map("Authorization" -> request.bearerToken)
)
}
}
39 changes: 39 additions & 0 deletions src/main/scala/dev/habla/twitter/v2_requests/lookupuser/From.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package dev.habla.twitter
package v2_requests
package lookupuser

import dev.habla.twitter.v2
import dev.habla.twitter.v2.lookupuser._
import spray.json.JsValue

import scala.util.Try


trait From extends HttpBody with RateLimitHeaders{

def from(response: requests.Response): Response = {
val bodyE = parseBody(response)
parseTweetInfo(response, bodyE)
.orElse(parseRateLimitExceeded(response))
.orElse(parseErroneousTextResponse(bodyE))
.orElse(parseErroneousJsonResponse(bodyE))
.getOrElse(ErroneousTextResponse("Not a lookup response"))
}

def parseTweetInfo(response: requests.Response, bodyE: Either[String,JsValue]): Option[UserInfo] =
for {
body <- bodyE.toOption if response.statusCode == 200
users <- Try(body.convertTo[UserInfo.Body]).toOption
(rateRemaining, rateReset) <- parseRateLimitHeaders(response)
} yield UserInfo(users, rateRemaining, rateReset)

def parseRateLimitExceeded(response: requests.Response): Option[RateLimitExceeded] =
if (response.statusCode != 429) None
else parseRateLimitHeaders(response).map{ case (_, l) => RateLimitExceeded(l) }

def parseErroneousTextResponse(body: Either[String, JsValue]): Option[ErroneousJsonResponse] =
body.toOption.map(ErroneousJsonResponse)

def parseErroneousJsonResponse(body: Either[String, JsValue]): Option[ErroneousTextResponse] =
body.swap.toOption.map(ErroneousTextResponse)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package dev.habla.twitter
package v2_requests
package lookupuser

object Run extends HttpEndpoint[v2.lookupuser.Request]
with From
with To{
type Response = v2.lookupuser.Response
}
Loading

0 comments on commit f6009b7

Please sign in to comment.