Skip to content

Commit

Permalink
Move away from Twitter's util-collection (close #42)
Browse files Browse the repository at this point in the history
  • Loading branch information
three authored and BenFradet committed Jun 29, 2018
1 parent 116595a commit 710eadf
Show file tree
Hide file tree
Showing 6 changed files with 41 additions and 136 deletions.
6 changes: 3 additions & 3 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ lazy val root = project
Dependencies.maxmind,
Dependencies.catsEffect,
Dependencies.cats,
Dependencies.scalaz,
Dependencies.specs2,
Dependencies.catsEffect
Dependencies.lruMap,
Dependencies.scalaCheck,
Dependencies.specs2
)
)
10 changes: 6 additions & 4 deletions project/Dependencies.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@
import sbt._

object Dependencies {
val maxmind = "com.maxmind.geoip2" % "geoip2" % "2.11.0"
val catsEffect = "org.typelevel" %% "cats-effect" % "0.10.1"
val cats = "org.typelevel" %% "cats-core" % "1.1.0"
val specs2 = "org.specs2" %% "specs2-core" % "4.0.3" % "test"
val maxmind = "com.maxmind.geoip2" % "geoip2" % "2.11.0"
val catsEffect = "org.typelevel" %% "cats-effect" % "0.10.1"
val cats = "org.typelevel" %% "cats-core" % "1.1.0"
val lruMap = "com.snowplowanalytics" %% "scala-lru-map" % "0.1.0"
val scalaCheck = "org.scalacheck" %% "scalacheck" % "1.14.0" % "test"
val specs2 = "org.specs2" %% "specs2-core" % "4.0.3" % "test"
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import java.util.{Collections, Map}
import com.maxmind.db.CHMCache
import com.maxmind.geoip2.model.CityResponse
import com.maxmind.geoip2.DatabaseReader
import com.snowplowanalytics.lrumap.LruMap
import cats.effect.Sync
import cats.syntax.either._
import cats.syntax.flatMap._
Expand Down Expand Up @@ -46,9 +47,9 @@ object IpLookups {
connectionTypeFile: Option[File] = None,
memCache: Boolean = true,
lruCacheSize: Int = 10000
): F[IpLookups] =
): F[IpLookups[F]] =
(
if (lruCacheSize >= 0)
if (lruCacheSize > 0)
Sync[F].map(LruMap.create[F, String, IpLookupResult](lruCacheSize))(Some(_))
else Sync[F].pure(None)
).flatMap((lruCache) =>
Expand Down Expand Up @@ -80,7 +81,7 @@ object IpLookups {
connectionTypeFile: Option[String] = None,
memCache: Boolean = true,
lruCacheSize: Int = 10000
): F[IpLookups] =
): F[IpLookups[F]] =
IpLookups.createFromFiles(
geoFile.map(new File(_)),
ispFile.map(new File(_)),
Expand All @@ -104,18 +105,14 @@ object IpLookups {
* Inspired by:
* https://github.com/jt6211/hadoop-dns-mining/blob/master/src/main/java/io/covert/dns/geo/IpLookups.java
*/
class IpLookups private (
class IpLookups[F[_]: Sync] private (
geoFile: Option[File],
ispFile: Option[File],
domainFile: Option[File],
connectionTypeFile: Option[File],
memCache: Boolean,
lruCache: Option[LruMap[String, IpLookupResult]]
lru: Option[LruMap[F, String, IpLookupResult]]
) {

// Initialise the cache
private val lru = lruCache

// Configure the lookup services
private val geoService = getService(geoFile)
private val ispService = getService(ispFile).map(SpecializedReader(_, ReaderFunctions.isp))
Expand All @@ -141,11 +138,11 @@ class IpLookups private (
}

/**
* Creates a Validation from an IPLookup
* Creates an Either from an IPLookup
* @param service ISP, domain or connection type LookupService
* @return the result of the lookup
*/
private def getLookup[F[_]: Sync](
private def getLookup(
ipAddress: Either[Throwable, InetAddress],
service: Option[SpecializedReader]
): F[Option[Either[Throwable, String]]] =
Expand All @@ -163,12 +160,12 @@ class IpLookups private (
* as an IpLocation, or None if MaxMind cannot find
* the location.
*/
def performLookups[F[_]: Sync](s: String): F[IpLookupResult] =
def performLookups(s: String): F[IpLookupResult] =
lru
.map(performLookupsWithLruCache(_, s))
.getOrElse(performLookupsWithoutLruCache(s))

private def getLocationLookup[F[_]: Sync](
private def getLocationLookup(
ipAddress: Either[Throwable, InetAddress]
): F[Option[Either[Throwable, IpLocation]]] = (ipAddress, geoService) match {
case (Right(ipA), Some(gs)) =>
Expand All @@ -189,7 +186,7 @@ class IpLookups private (
* @return Tuple containing the results of the
* LookupServices
*/
private def performLookupsWithoutLruCache[F[_]: Sync](ip: String): F[IpLookupResult] =
private def performLookupsWithoutLruCache(ip: String): F[IpLookupResult] =
for {
ipAddress <- getIpAddress(ip)

Expand All @@ -211,26 +208,26 @@ class IpLookups private (
* cache entry could be found), versus an extant cache entry
* containing None (meaning that the IP address is unknown).
*/
private def performLookupsWithLruCache[F[_]: Sync](
lru: LruMap[String, IpLookupResult],
private def performLookupsWithLruCache(
lru: LruMap[F, String, IpLookupResult],
ip: String
): F[IpLookupResult] = {
val lookupAndCache =
performLookupsWithoutLruCache(ip).flatMap(result => {
LruMap.put(lru, ip, result).map(_ => result)
lru.put(ip, result).map(_ => result)
})

LruMap
.get(lru, ip)
lru
.get(ip)
.map(_.map(Sync[F].pure(_)))
.flatMap(_.getOrElse(lookupAndCache))
}

/** Transforms a String into an Either[Throwable, InetAddress] */
private def getIpAddress[F[_]: Sync](ip: String): F[Either[Throwable, InetAddress]] =
private def getIpAddress(ip: String): F[Either[Throwable, InetAddress]] =
Sync[F].delay { Either.catchNonFatal(InetAddress.getByName(ip)) }

private def getCityResponse[F[_]: Sync](
private def getCityResponse(
gs: DatabaseReader,
ipAddress: InetAddress
): F[Either[Throwable, CityResponse]] =
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import cats.syntax.either._
final case class SpecializedReader(db: DatabaseReader, f: ReaderFunction) {
def getValue[F[_]: Sync](ip: InetAddress): F[Either[Throwable, String]] =
Sync[F].delay { Either.catchNonFatal(f(db, ip)) }
}

object ReaderFunctions {
type ReaderFunction = (DatabaseReader, InetAddress) => String
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import java.net.UnknownHostException

import com.maxmind.geoip2.exception.AddressNotFoundException
import org.specs2.mutable.Specification
import org.specs2.specification.Tables
import cats.syntax.either._
import cats.syntax.option._
import cats.effect.IO
Expand All @@ -24,7 +25,7 @@ import model._

object IpLookupsTest {

def ipLookupsFromFiles(memCache: Boolean, lruCache: Int): IpLookups = {
def ipLookupsFromFiles(memCache: Boolean, lruCache: Int): IpLookups[IO] = {
val geoFile = getClass.getResource("GeoIP2-City-Test.mmdb").getFile
val ispFile = getClass.getResource("GeoIP2-ISP-Test.mmdb").getFile
val domainFile = getClass.getResource("GeoIP2-Domain-Test.mmdb").getFile
Expand Down Expand Up @@ -109,7 +110,7 @@ object IpLookupsTest {
)
}

class IpLookupsTest extends Specification {
class IpLookupsTest extends Specification with Tables {

"Looking up some IP address locations should match their expected locations" should {

Expand All @@ -135,7 +136,7 @@ class IpLookupsTest extends Specification {
testData foreach {
case (ip, expected) =>
formatter(ip, memCache, lruCache) should {
val actual = ipLookups.performLookups[IO](ip).unsafeRunSync()
val actual = ipLookups.performLookups(ip).unsafeRunSync
matchIpLookupResult(actual, expected)
}
}
Expand All @@ -150,31 +151,28 @@ class IpLookupsTest extends Specification {
new UnknownHostException("not: Name or service not known").asLeft.some,
new UnknownHostException("not: Name or service not known").asLeft.some
)
val actual = ipLookups.performLookups[IO]("not").unsafeRunSync
val actual = ipLookups.performLookups("not").unsafeRunSync
matchIpLookupResult(actual, expected)
}

"providing no files should return Nones" in {
val actual = (for {
ipLookups <- IpLookups.createFromFiles[IO](None, None, None, None, true, 0)
res <- ipLookups.performLookups[IO]("67.43.156.0")
res <- ipLookups.performLookups("67.43.156.0")
} yield res).unsafeRunSync
val expected = IpLookupResult(None, None, None, None, None)
matchIpLookupResult(actual, expected)
}
}

private def matchIpLookupResult(actual: IpLookupResult, expected: IpLookupResult) = {
s"have iplocation = ${actual.ipLocation}" in {
matchThrowables(actual.ipLocation, expected.ipLocation)
}
s"have isp = ${actual.isp}" in { matchThrowables(actual.isp, expected.isp) }
s"have org = ${actual.organization}" in {
matchThrowables(actual.organization, expected.organization)
}
s"have domain = ${actual.domain}" in { matchThrowables(actual.domain, expected.domain) }
s"have net speed = ${actual.connectionType}" in {
matchThrowables(actual.connectionType, expected.connectionType)
"field" | "expected" | "actual" |>
"iplocation" ! expected.ipLocation ! actual.ipLocation |
"isp" ! expected.isp ! actual.isp |
"organization" ! expected.organization ! actual.organization |
"domain" ! expected.domain ! actual.domain |
"connection type" ! expected.connectionType ! actual.connectionType | { (_, e, a) =>
matchThrowables(e, a)
}
}

Expand Down

0 comments on commit 710eadf

Please sign in to comment.