Skip to content

Commit

Permalink
Support cats.Id (closes #113)
Browse files Browse the repository at this point in the history
  • Loading branch information
BenFradet authored and dilyand committed Nov 27, 2019
1 parent 3502239 commit 6c95932
Show file tree
Hide file tree
Showing 6 changed files with 71 additions and 27 deletions.
2 changes: 1 addition & 1 deletion project/Dependencies.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ object Dependencies {
val circe = "0.11.1"
val scalaj = "2.4.1"
val lruMap = "0.3.0"

// Tests
val specs2 = "4.3.5"
}
Expand Down
32 changes: 23 additions & 9 deletions src/main/scala/com.snowplowanalytics/weather/Transport.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ package com.snowplowanalytics.weather

import scala.concurrent.duration.FiniteDuration

import cats.Eval
import cats.{Eval, Id}
import cats.effect.Sync
import cats.syntax.either._
import io.circe.{Decoder, Json}
Expand All @@ -32,7 +32,7 @@ trait Transport[F[_]] {
* @param request request built by client method
* @param apiHost address of the API to interrogate
* @param apiKey credentials to interrogate the API
* @param requestTimeout duration after which the request will be timed out
* @param timeout duration after which the request will be timed out
* @param ssl whether to use https or http
* @tparam W type of weather response to extract
* @return extracted either error or weather wrapped in `F`
Expand All @@ -41,7 +41,7 @@ trait Transport[F[_]] {
request: WeatherRequest,
apiHost: String,
apiKey: String,
requestTimeout: FiniteDuration,
timeout: FiniteDuration,
ssl: Boolean
): F[Either[WeatherError, W]]
}
Expand All @@ -54,10 +54,10 @@ object Transport {
request: WeatherRequest,
apiHost: String,
apiKey: String,
requestTimeout: FiniteDuration,
timeout: FiniteDuration,
ssl: Boolean
): F[Either[WeatherError, W]] =
Sync[F].delay(buildRequest(apiHost, apiKey, ssl, request))
Sync[F].delay(getResponse(apiHost, apiKey, ssl, timeout, request))
}

/** Eval http Transport in cases where you have to do side-effects (e.g. spark). */
Expand All @@ -66,22 +66,36 @@ object Transport {
request: WeatherRequest,
apiHost: String,
apiKey: String,
requestTimeout: FiniteDuration,
timeout: FiniteDuration,
ssl: Boolean
): Eval[Either[WeatherError, W]] = Eval.later {
buildRequest(apiHost, apiKey, ssl, request)
getResponse(apiHost, apiKey, ssl, timeout, request)
}
}

private def buildRequest[W <: WeatherResponse: Decoder](
/** Id http Transport in cases where you have to do side-effects (e.g. spark). */
implicit def idTransport: Transport[Id] = new Transport[Id] {
def receive[W <: WeatherResponse: Decoder](
request: WeatherRequest,
apiHost: String,
apiKey: String,
timeout: FiniteDuration,
ssl: Boolean
): Id[Either[WeatherError, W]] = getResponse(apiHost, apiKey, ssl, timeout, request)
}

private def getResponse[W <: WeatherResponse: Decoder](
apiHost: String,
apiKey: String,
ssl: Boolean,
timeout: FiniteDuration,
request: WeatherRequest
): Either[WeatherError, W] = {
val scheme = if (ssl) "https" else "http"
val baseUri = s"$scheme://$apiHost"
val uri = request.constructRequest(baseUri, apiKey)
val uri = request
.constructRequest(baseUri, apiKey)
.timeout(timeout.toMillis.toInt, timeout.toMillis.toInt)

for {
response <- Either.catchNonFatal(uri.asString).leftMap(e => InternalError(e.getMessage))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ package providers.darksky

import scala.concurrent.duration._

import cats.{Eval, Monad}
import cats.{Eval, Id, Monad}
import cats.data.EitherT
import cats.effect.Sync
import cats.syntax.either._
Expand Down Expand Up @@ -92,6 +92,23 @@ object CreateDarkSky {
cacheClient[Eval](apiHost, apiKey, timeout, cacheSize, geoPrecision, ssl = true)
}

implicit def idCreateDarkSky(implicit T: Transport[Id]): CreateDarkSky[Id] =
new CreateDarkSky[Id] {
override def create(
apiHost: String,
apiKey: String,
timeout: FiniteDuration
): DarkSkyClient[Id] = new DarkSkyClient[Id](apiHost, apiKey, timeout, ssl = true)
override def create(
apiHost: String,
apiKey: String,
timeout: FiniteDuration,
cacheSize: Int,
geoPrecision: Int
): Id[Either[InvalidConfigurationError, DarkSkyCacheClient[Id]]] =
cacheClient[Id](apiHost, apiKey, timeout, cacheSize, geoPrecision, ssl = true)
}

private[darksky] def cacheClient[F[_]: Monad](
apiHost: String,
apiKey: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,14 @@ import requests.DarkSkyRequest
* For more detailed description go to: https://darksky.net/dev/docs
* @param apiHost address of the API to interrogate
* @param apiKey credentials to interrogate the API
* @param requestTimeout duration after which the request will be timed out
* @param timeout duration after which the request will be timed out
* @param ssl whether to use https or http
* @tparam F effect type
*/
class DarkSkyClient[F[_]] private[darksky] (
apiHost: String,
apiKey: String,
requestTimeout: FiniteDuration,
timeout: FiniteDuration,
ssl: Boolean
)(implicit T: Transport[F]) {

Expand All @@ -61,7 +61,7 @@ class DarkSkyClient[F[_]] private[darksky] (
units: Option[Units] = None
): F[Either[WeatherError, DarkSkyResponse]] = {
val request = DarkSkyRequest(latitude, longitude, None, exclude, extend, lang, units)
T.receive(request, apiHost, apiKey, requestTimeout, ssl)
T.receive(request, apiHost, apiKey, timeout, ssl)
}

/** "Time Machine Request" - returns the observed (in the past) or forecasted (in the future)
Expand All @@ -87,6 +87,6 @@ class DarkSkyClient[F[_]] private[darksky] (
units: Option[Units] = None
): F[Either[WeatherError, DarkSkyResponse]] = {
val request = DarkSkyRequest(latitude, longitude, Some(dateTime.toEpochSecond), exclude, extend, lang, units)
T.receive(request, apiHost, apiKey, requestTimeout, ssl)
T.receive(request, apiHost, apiKey, timeout, ssl)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ package providers.openweather

import scala.concurrent.duration._

import cats.{Eval, Monad}
import cats.{Eval, Id, Monad}
import cats.data.EitherT
import cats.effect.Sync
import cats.syntax.either._
Expand Down Expand Up @@ -86,6 +86,19 @@ object CreateOWM {
cacheClient[Eval](apiHost, apiKey, timeout, cacheSize, geoPrecision, ssl = true)
}

implicit def idCreateOWM(implicit T: Transport[Id]): CreateOWM[Id] = new CreateOWM[Id] {
def create(apiHost: String, apiKey: String, timeout: FiniteDuration): OWMClient[Id] =
new OWMClient(apiHost, apiKey, timeout, ssl = true)
def create(
apiHost: String,
apiKey: String,
timeout: FiniteDuration,
cacheSize: Int,
geoPrecision: Int
): Id[Either[InvalidConfigurationError, OWMCacheClient[Id]]] =
cacheClient[Id](apiHost, apiKey, timeout, cacheSize, geoPrecision, ssl = true)
}

private[openweather] def cacheClient[F[_]: Monad](
apiHost: String,
apiKey: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,14 @@ import requests._
* Non-caching OpenWeatherMap client
* @param apiHost address of the API to interrogate
* @param apiKey credentials to interrogate the API
* @param requestTimeout duration after which the request will be timed out
* @param timeout duration after which the request will be timed out
* @param ssl whether to use https or http
* @tparam F effect type
*/
class OWMClient[F[_]] private[openweather] (
apiHost: String,
apiKey: String,
requestTimeout: FiniteDuration,
timeout: FiniteDuration,
ssl: Boolean
)(implicit T: Transport[F]) {

Expand Down Expand Up @@ -61,7 +61,7 @@ class OWMClient[F[_]] private[openweather] (
++ ("cnt" -> cnt)
++ ("type" -> measure.map(_.toString))
)
T.receive(request, apiHost, apiKey, requestTimeout, ssl)
T.receive(request, apiHost, apiKey, timeout, ssl)
}

/**
Expand Down Expand Up @@ -93,7 +93,7 @@ class OWMClient[F[_]] private[openweather] (
++ ("cnt" -> cnt)
++ ("type" -> measure.map(_.toString))
)
T.receive(request, apiHost, apiKey, requestTimeout, ssl)
T.receive(request, apiHost, apiKey, timeout, ssl)
}

/**
Expand Down Expand Up @@ -122,7 +122,7 @@ class OWMClient[F[_]] private[openweather] (
++ ("end" -> end)
++ ("cnt" -> cnt)
++ ("type" -> measure.map(_.toString)))
T.receive(request, apiHost, apiKey, requestTimeout, ssl)
T.receive(request, apiHost, apiKey, timeout, ssl)
}

/**
Expand All @@ -134,7 +134,7 @@ class OWMClient[F[_]] private[openweather] (
*/
def forecastById(id: Int, cnt: OptArg[Int] = None): F[Either[WeatherError, Forecast]] = {
val request = OwmForecastRequest("city", Map("id" -> id.toString, "cnt" -> cnt.toString))
T.receive(request, apiHost, apiKey, requestTimeout, ssl)
T.receive(request, apiHost, apiKey, timeout, ssl)
}

/**
Expand All @@ -153,7 +153,7 @@ class OWMClient[F[_]] private[openweather] (
): F[Either[WeatherError, Forecast]] = {
val query = name + country.map("," + _).getOrElse("")
val request = OwmForecastRequest("city", Map("q" -> query, "cnt" -> cnt.toString))
T.receive(request, apiHost, apiKey, requestTimeout, ssl)
T.receive(request, apiHost, apiKey, timeout, ssl)
}

/**
Expand All @@ -170,7 +170,7 @@ class OWMClient[F[_]] private[openweather] (
): F[Either[WeatherError, Weather]] = {
val request =
OwmForecastRequest("weather", Map("lat" -> lat.toString, "lon" -> lon.toString, "cnt" -> cnt.toString))
T.receive(request, apiHost, apiKey, requestTimeout, ssl)
T.receive(request, apiHost, apiKey, timeout, ssl)
}

/**
Expand All @@ -182,7 +182,7 @@ class OWMClient[F[_]] private[openweather] (
*/
def currentById(id: Int): F[Either[WeatherError, Current]] = {
val request = OwmCurrentRequest("weather", Map("id" -> id.toString))
T.receive(request, apiHost, apiKey, requestTimeout, ssl)
T.receive(request, apiHost, apiKey, timeout, ssl)
}

/**
Expand All @@ -201,7 +201,7 @@ class OWMClient[F[_]] private[openweather] (
): F[Either[WeatherError, Current]] = {
val query = name + country.map("," + _).getOrElse("")
val request = OwmCurrentRequest("weather", Map("q" -> query, "cnt" -> cnt.toString))
T.receive(request, apiHost, apiKey, requestTimeout, ssl)
T.receive(request, apiHost, apiKey, timeout, ssl)
}

/**
Expand All @@ -214,7 +214,7 @@ class OWMClient[F[_]] private[openweather] (
*/
def currentByCoords(lat: Float, lon: Float): F[Either[WeatherError, Current]] = {
val request = OwmCurrentRequest("weather", Map("lat" -> lat.toString, "lon" -> lon.toString))
T.receive(request, apiHost, apiKey, requestTimeout, ssl)
T.receive(request, apiHost, apiKey, timeout, ssl)
}

}

0 comments on commit 6c95932

Please sign in to comment.