Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Apps should explicitly import java.net.http.HttpClient instances of RegistryLookup #242

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,7 @@ import cats.syntax.show._

import com.snowplowanalytics.iglu.client.Client
import com.snowplowanalytics.iglu.core.{SchemaKey, SchemaVer, SelfDescribingData}

implicit val clockIoInstance: Clock[IO] = Clock.create[IO] // Usually provided by IOApp
import com.snowplowanalytics.iglu.client.resolver.registries.JavaNetRegistryLookup._

val resolverConfig: Json = json"""{
"schema": "iglu:com.snowplowanalytics.iglu/resolver-config/jsonschema/1-0-1",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
/*
* Copyright (c) 2014-2023 Snowplow Analytics Ltd. All rights reserved.
*
* This program is licensed to you under the Apache License Version 2.0,
* and you may not use this file except in compliance with the Apache License Version 2.0.
* You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the Apache License Version 2.0 is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
*/
package com.snowplowanalytics.iglu.client.resolver.registries

import cats.effect.Sync
import cats.Id
import cats.data.OptionT
import cats.implicits._
import io.circe.Json
import io.circe.parser.parse

import com.snowplowanalytics.iglu.core.circe.implicits._
import com.snowplowanalytics.iglu.core.{SchemaKey, SchemaList}

import java.net.UnknownHostException
import scala.util.control.NonFatal

object JavaNetRegistryLookup {
istreeter marked this conversation as resolved.
Show resolved Hide resolved

implicit def ioLookupInstance[F[_]](implicit F: Sync[F]): RegistryLookup[F] =
new RegistryLookup[F] {
def lookup(repositoryRef: Registry, schemaKey: SchemaKey): F[Either[RegistryError, Json]] =
repositoryRef match {
case Registry.Http(_, connection) => httpLookup(connection, schemaKey)
case Registry.Embedded(_, path) => RegistryLookup.embeddedLookup[F](path, schemaKey)
case Registry.InMemory(_, schemas) =>
F.delay(RegistryLookup.inMemoryLookup(schemas, schemaKey))
}

def list(
registry: Registry,
vendor: String,
name: String,
model: Int
): F[Either[RegistryError, SchemaList]] =
registry match {
case Registry.Http(_, connection) => httpList(connection, vendor, name, model)
case Registry.Embedded(_, base) =>
val path = RegistryLookup.toSubpath(base, vendor, name)
Sync[F].delay(Utils.unsafeEmbeddedList(path, model))
case _ => F.pure(RegistryError.NotFound.asLeft)
}
}

// Id instance also swallows all exceptions into `RegistryError`
implicit def idLookupInstance: RegistryLookup[Id] =
new RegistryLookup[Id] {
def lookup(repositoryRef: Registry, schemaKey: SchemaKey): Id[Either[RegistryError, Json]] =
repositoryRef match {
case Registry.Http(_, connection) =>
Utils
.stringToUri(RegistryLookup.toPath(connection.uri.toString, schemaKey))
.flatMap(uri => Utils.unsafeGetFromUri(uri, connection.apikey))
case Registry.Embedded(_, base) =>
val path = RegistryLookup.toPath(base, schemaKey)
Utils.unsafeEmbeddedLookup(path)
case Registry.InMemory(_, schemas) =>
RegistryLookup.inMemoryLookup(schemas, schemaKey)
}

def list(
registry: Registry,
vendor: String,
name: String,
model: Int
): Id[Either[RegistryError, SchemaList]] =
registry match {
case Registry.Http(_, connection) =>
val subpath = RegistryLookup.toSubpath(connection.uri.toString, vendor, name, model)
Utils.stringToUri(subpath).flatMap(Utils.unsafeHttpList(_, connection.apikey))
case Registry.Embedded(_, base) =>
val path = RegistryLookup.toSubpath(base, vendor, name)
Utils.unsafeEmbeddedList(path, model)
case _ =>
RegistryError.NotFound.asLeft
}
}

/**
* Retrieves an Iglu Schema from the HTTP Iglu Repo as a JSON
*
* @param http endpoint and optional apikey
* @param key The SchemaKey uniquely identifying the schema in Iglu
* @return either a `Json` on success, or `RegistryError` in case of any failure
* (i.e. all exceptions should be swallowed by `RegistryError`)
*/
private[registries] def httpLookup[F[_]: Sync](
http: Registry.HttpConnection,
key: SchemaKey
): F[Either[RegistryError, Json]] =
Utils
.stringToUri(RegistryLookup.toPath(http.uri.toString, key))
.traverse(uri => Utils.getFromUri(uri, http.apikey))
istreeter marked this conversation as resolved.
Show resolved Hide resolved
.map { response =>
val result = for {
body <- OptionT(response)
json = parse(body)
result <- OptionT.liftF[Either[RegistryError, *], Json](
json.leftMap(e => RegistryError.RepoFailure(e.show))
)
} yield result

result.getOrElseF[Json](RegistryError.NotFound.asLeft)
}
.recover {
case uhe: UnknownHostException =>
val error = s"Unknown host issue fetching: ${uhe.getMessage}"
RegistryError.RepoFailure(error).asLeft
case NonFatal(nfe) =>
val error = s"Unexpected exception fetching: $nfe"
RegistryError.RepoFailure(error).asLeft
}

private[registries] def httpList[F[_]: Sync](
http: Registry.HttpConnection,
vendor: String,
name: String,
model: Int
): F[Either[RegistryError, SchemaList]] =
Utils
.stringToUri(RegistryLookup.toSubpath(http.uri.toString, vendor, name, model))
.traverse(uri => Utils.getFromUri(uri, http.apikey))
.map { response =>
for {
body <- response
text <- body.toRight(RegistryError.NotFound)
json <- parse(text).leftMap(e => RegistryError.RepoFailure(e.show))
list <- json.as[SchemaList].leftMap(e => RegistryError.RepoFailure(e.show))
} yield list
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,9 @@
package com.snowplowanalytics.iglu.client.resolver
package registries

// Java
import cats.effect.Sync

import java.net.UnknownHostException

// Scala
import scala.util.control.NonFatal

// cats
import cats.Id
import cats.data.{EitherT, OptionT}
import cats.effect.Sync
import cats.data.EitherT
import cats.effect.implicits._
import cats.implicits._

Expand Down Expand Up @@ -88,64 +80,6 @@ object RegistryLookup {
RegistryLookup[F].list(repositoryRef, vendor: String, name: String, model: Int)
}

implicit def ioLookupInstance[F[_]](implicit F: Sync[F]): RegistryLookup[F] =
new RegistryLookup[F] {
def lookup(repositoryRef: Registry, schemaKey: SchemaKey): F[Either[RegistryError, Json]] =
repositoryRef match {
case Registry.Http(_, connection) => httpLookup(connection, schemaKey)
case Registry.Embedded(_, path) => embeddedLookup[F](path, schemaKey)
case Registry.InMemory(_, schemas) => F.pure(inMemoryLookup(schemas, schemaKey))
}

def list(
registry: Registry,
vendor: String,
name: String,
model: Int
): F[Either[RegistryError, SchemaList]] =
registry match {
case Registry.Http(_, connection) => httpList(connection, vendor, name, model)
case Registry.Embedded(_, base) =>
val path = toSubpath(base, vendor, name)
Sync[F].delay(Utils.unsafeEmbeddedList(path, model))
case _ => F.pure(RegistryError.NotFound.asLeft)
}
}

// Id instance also swallows all exceptions into `RegistryError`
implicit def idLookupInstance: RegistryLookup[Id] =
new RegistryLookup[Id] {
def lookup(repositoryRef: Registry, schemaKey: SchemaKey): Id[Either[RegistryError, Json]] =
repositoryRef match {
case Registry.Http(_, connection) =>
Utils
.stringToUri(toPath(connection.uri.toString, schemaKey))
.flatMap(uri => Utils.unsafeGetFromUri(uri, connection.apikey))
case Registry.Embedded(_, base) =>
val path = toPath(base, schemaKey)
Utils.unsafeEmbeddedLookup(path)
case Registry.InMemory(_, schemas) =>
inMemoryLookup(schemas, schemaKey)
}

def list(
registry: Registry,
vendor: String,
name: String,
model: Int
): Id[Either[RegistryError, SchemaList]] =
registry match {
case Registry.Http(_, connection) =>
val subpath = toSubpath(connection.uri.toString, vendor, name, model)
Utils.stringToUri(subpath).flatMap(Utils.unsafeHttpList(_, connection.apikey))
case Registry.Embedded(_, base) =>
val path = toSubpath(base, vendor, name)
Utils.unsafeEmbeddedList(path, model)
case _ =>
RegistryError.NotFound.asLeft
}
}

def inMemoryLookup(
schemas: List[SelfDescribingSchema[Json]],
key: SchemaKey
Expand All @@ -156,15 +90,15 @@ object RegistryLookup {
private[registries] def toPath(prefix: String, key: SchemaKey): String =
s"${prefix.stripSuffix("/")}/schemas/${key.toPath}"

private def toSubpath(
private[registries] def toSubpath(
pondzix marked this conversation as resolved.
Show resolved Hide resolved
prefix: String,
vendor: String,
name: String,
model: Int
): String =
s"${prefix.stripSuffix("/")}/schemas/$vendor/$name/jsonschema/$model"

private def toSubpath(
private[registries] def toSubpath(
prefix: String,
vendor: String,
name: String
Expand Down Expand Up @@ -195,56 +129,4 @@ object RegistryLookup {
result.value
}

/**
* Retrieves an Iglu Schema from the HTTP Iglu Repo as a JSON
*
* @param http endpoint and optional apikey
* @param key The SchemaKey uniquely identifying the schema in Iglu
* @return either a `Json` on success, or `RegistryError` in case of any failure
* (i.e. all exceptions should be swallowed by `RegistryError`)
*/
private[registries] def httpLookup[F[_]: Sync](
http: Registry.HttpConnection,
key: SchemaKey
): F[Either[RegistryError, Json]] =
Utils
.stringToUri(toPath(http.uri.toString, key))
.traverse(uri => Utils.getFromUri(uri, http.apikey))
.map { response =>
val result = for {
body <- OptionT(response)
json = parse(body)
result <- OptionT.liftF[Either[RegistryError, *], Json](
json.leftMap(e => RegistryError.RepoFailure(e.show))
)
} yield result

result.getOrElseF[Json](RegistryError.NotFound.asLeft)
}
.recover {
case uhe: UnknownHostException =>
val error = s"Unknown host issue fetching: ${uhe.getMessage}"
RegistryError.RepoFailure(error).asLeft
case NonFatal(nfe) =>
val error = s"Unexpected exception fetching: $nfe"
RegistryError.RepoFailure(error).asLeft
}

private[registries] def httpList[F[_]: Sync](
http: Registry.HttpConnection,
vendor: String,
name: String,
model: Int
): F[Either[RegistryError, SchemaList]] =
Utils
.stringToUri(toSubpath(http.uri.toString, vendor, name, model))
.traverse(uri => Utils.getFromUri(uri, http.apikey))
.map { response =>
for {
body <- response
text <- body.toRight(RegistryError.NotFound)
json <- parse(text).leftMap(e => RegistryError.RepoFailure(e.show))
list <- json.as[SchemaList].leftMap(e => RegistryError.RepoFailure(e.show))
} yield list
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import cats.effect.testing.specs2.CatsEffect
import io.circe.literal._

import com.snowplowanalytics.iglu.core.{SchemaKey, SchemaVer, SelfDescribingData}
import com.snowplowanalytics.iglu.client.resolver.registries.JavaNetRegistryLookup._

// Specs2
import org.specs2.mutable.Specification
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@
package com.snowplowanalytics.iglu.client

import cats.Applicative
import com.snowplowanalytics.iglu.client.resolver.registries.{RegistryError, RegistryLookup}
import com.snowplowanalytics.iglu.client.resolver.registries.{
JavaNetRegistryLookup,
RegistryError,
RegistryLookup
}
import com.snowplowanalytics.iglu.core.{SchemaKey, SchemaList}
import io.circe.Json

Expand Down Expand Up @@ -61,7 +65,7 @@ object SpecHelpers {
Seq(registry.config.name, schemaKey.toSchemaUri).mkString("-") :: l
)
) >>
RegistryLookup.ioLookupInstance[IO].lookup(registry, schemaKey)
JavaNetRegistryLookup.ioLookupInstance[IO].lookup(registry, schemaKey)
}

override def list(
Expand All @@ -75,7 +79,7 @@ object SpecHelpers {
Seq(registry.config.name, vendor, name, model.toString).mkString("-") :: l
)
) >>
RegistryLookup.ioLookupInstance[IO].list(registry, vendor, name, model)
JavaNetRegistryLookup.ioLookupInstance[IO].list(registry, vendor, name, model)
}
}
def mkTrackingRegistry: TrackingRegistry = TrackingRegistry(
Expand Down
Loading
Loading