Skip to content

Commit

Permalink
feat(apollo): add schema registry to the agent using Tapir library. A…
Browse files Browse the repository at this point in the history
…TL-1334 (#94)

* [ATL-2014] build: External registry set to domain name
* feat(pollux): implement schema-registry in-memory using Tapir
Co-authored-by: David <david.poltorak@iohk.io>
  • Loading branch information
yshyn-iohk committed Nov 22, 2022
1 parent 328c6a9 commit b3cf828
Show file tree
Hide file tree
Showing 17 changed files with 434 additions and 14 deletions.
8 changes: 8 additions & 0 deletions infrastructure/local/haproxy/haproxy.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ frontend https-in
use_backend mediator if { path_beg -i /mediator }
use_backend swagger-ui if { path_beg -i /apidocs }
use_backend prism-agent if { path_beg -i /prism-agent }
use_backend prism-agent-tapir if { path_beg -i /tapir }

backend mediator
balance roundrobin
Expand All @@ -42,6 +43,13 @@ backend prism-agent
option forwardfor
server s1 prism-agent:8080 maxconn 32

backend prism-agent-tapir
balance roundrobin
http-request set-uri %[url,regsub(^/tapir,,)] if { path_beg /tapir }
option httpclose
option forwardfor
server s1 prism-agent:8085 maxconn 32

backend swagger-ui
balance roundrobin
option httpclose
Expand Down
2 changes: 1 addition & 1 deletion prism-agent/service/build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ lazy val server = project
Docker / dockerUsername := Some("input-output-hk"),
Docker / githubOwner := "atala-prism-building-blocks",
Docker / dockerRepository := Some("ghcr.io"),
dockerExposedPorts := Seq(8080),
dockerExposedPorts := Seq(8080, 8085),
dockerBaseImage := "openjdk:11"
)
.enablePlugins(OpenApiGeneratorPlugin, JavaAppPackaging, DockerPlugin)
Expand Down
42 changes: 34 additions & 8 deletions prism-agent/service/project/Dependencies.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,24 @@ object Dependencies {
val zio = "2.0.2"
val zioConfig = "3.0.2"
val zioHttp = "2.0.0-RC11"
val zioInteropCats = "3.3.0"
val akka = "2.6.20"
val akkaHttp = "10.2.9"
val castor = "0.2.0"
val pollux = "0.3.0"
val bouncyCastle = "1.70"
val logback = "1.4.4"
val mercury = "0.6.0"
val zioJson = "0.3.0"
val tapir = "1.2.2"
}

private lazy val zio = "dev.zio" %% "zio" % Versions.zio
private lazy val zioConfig = "dev.zio" %% "zio-config" % Versions.zioConfig
private lazy val zioConfigMagnolia = "dev.zio" %% "zio-config-magnolia" % Versions.zioConfig
private lazy val zioConfigTypesafe = "dev.zio" %% "zio-config-typesafe" % Versions.zioConfig
private lazy val zioJson = "dev.zio" %% "zio-json" % Versions.zioJson
private lazy val zioInteropCats = "dev.zio" %% "zio-interop-cats" % Versions.zioInteropCats

private lazy val zioTest = "dev.zio" %% "zio-test" % Versions.zio % Test
private lazy val zioTestSbt = "dev.zio" %% "zio-test-sbt" % Versions.zio % Test
Expand Down Expand Up @@ -45,26 +50,47 @@ object Dependencies {

private lazy val logback = "ch.qos.logback" % "logback-classic" % Versions.logback

private lazy val tapirSwaggerUiBundle = "com.softwaremill.sttp.tapir" %% "tapir-swagger-ui-bundle" % Versions.tapir
private lazy val tapirJsonZio = "com.softwaremill.sttp.tapir" %% "tapir-json-zio" % Versions.tapir

private lazy val tapirZioHttpServer = "com.softwaremill.sttp.tapir" %% "tapir-zio-http-server" % Versions.tapir
private lazy val tapirHttp4sServerZio = "com.softwaremill.sttp.tapir" %% "tapir-http4s-server-zio" % Versions.tapir
private lazy val http4sBlazeServer = "org.http4s" %% "http4s-blaze-server" % "0.23.12"

private lazy val tapirRedocBundle = "com.softwaremill.sttp.tapir" %% "tapir-redoc-bundle" % Versions.tapir
private lazy val tapirSttpStubServer = "com.softwaremill.sttp.tapir" %% "tapir-sttp-stub-server" % Versions.tapir % Test


// Dependency Modules
private lazy val baseDependencies: Seq[ModuleID] =
Seq(zio, zioTest, zioTestSbt, zioTestMagnolia, zioConfig, zioConfigMagnolia, zioConfigTypesafe)
private lazy val baseDependencies: Seq[ModuleID] = Seq(zio, zioTest, zioTestSbt, zioTestMagnolia, zioConfig, zioConfigMagnolia, zioConfigTypesafe, zioJson, logback, zioHttp)
private lazy val castorDependencies: Seq[ModuleID] = Seq(castorCore, castorSqlDoobie)
private lazy val polluxDependencies: Seq[ModuleID] = Seq(polluxCore, polluxSqlDoobie)
private lazy val mercuryDependencies: Seq[ModuleID] = Seq(mercuryAgent)
private lazy val akkaHttpDependencies: Seq[ModuleID] =
Seq(akkaTyped, akkaStream, akkaHttp, akkaSprayJson).map(_.cross(CrossVersion.for3Use2_13))
private lazy val bouncyDependencies: Seq[ModuleID] = Seq(bouncyBcpkix, bouncyBcprov)
private lazy val tapirDependencies: Seq[ModuleID] =
Seq(
tapirSwaggerUiBundle,
tapirJsonZio,
tapirRedocBundle,
tapirSttpStubServer,
tapirZioHttpServer,
tapirHttp4sServerZio,
http4sBlazeServer)


// Project Dependencies
lazy val keyManagementDependencies: Seq[ModuleID] = baseDependencies ++ castorDependencies ++ bouncyDependencies
lazy val keyManagementDependencies: Seq[ModuleID] =
baseDependencies ++
castorDependencies ++
bouncyDependencies

lazy val serverDependencies: Seq[ModuleID] =
baseDependencies ++
baseDependencies ++
akkaHttpDependencies ++
castorDependencies ++
polluxDependencies ++
mercuryDependencies ++
Seq(
zioHttp,
logback
)
tapirDependencies
}
2 changes: 1 addition & 1 deletion prism-agent/service/project/build.properties
Original file line number Diff line number Diff line change
@@ -1 +1 @@
sbt.version=1.7.1
sbt.version=1.7.2
1 change: 1 addition & 0 deletions prism-agent/service/project/build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.6")
addSbtPlugin("com.codecommit" % "sbt-github-packages" % "0.5.3")
addSbtPlugin("com.github.sbt" % "sbt-native-packager" % "1.9.9")
addSbtPlugin("com.github.sbt" % "sbt-release" % "1.1.0")
addDependencyTreePlugin

libraryDependencies ++= Seq("org.openapitools" % "openapi-generator" % "6.0.0")
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,13 @@ pollux {
password = "postgres"
password = ${?POLLUX_DB_PASSWORD}
}
}

agent {
httpEndpoint {
http {
port = 8085
port =${?AGENT_HTTP_PORT}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,13 @@ object Main extends ZIOAppDefault {
.debug
.fork

_ <- Modules.app(restServicePort).provide(didCommLayer)
_ <- Modules
.app(restServicePort)
.provide(didCommLayer)
.fork

_ <- Modules.zioApp.fork
_ <- ZIO.never
} yield ()

}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import akka.actor.typed.ActorSystem
import akka.actor.typed.scaladsl.Behaviors
import akka.http.scaladsl.server.Route
import doobie.util.transactor.Transactor
import io.iohk.atala.agent.server.http.{HttpRoutes, HttpServer}
import io.iohk.atala.agent.server.http.{HttpRoutes, HttpServer, ZHttp4sBlazeServer, ZHttpEndpoints}
import io.iohk.atala.castor.core.service.{DIDService, DIDServiceImpl}
import io.iohk.atala.agent.server.http.marshaller.{
DIDApiMarshallerImpl,
Expand Down Expand Up @@ -73,6 +73,9 @@ import io.iohk.atala.mercury.protocol.issuecredential._
import io.iohk.atala.pollux.core.model.error.IssueCredentialError
import io.iohk.atala.pollux.core.model.error.IssueCredentialError.RepositoryError
import java.io.IOException
import cats.implicits.*
import io.iohk.atala.pollux.schema.SchemaRegistryServerEndpoints
import io.iohk.atala.pollux.service.SchemaRegistryServiceInMemory

object Modules {

Expand All @@ -84,6 +87,19 @@ object Modules {
.unit
}

lazy val zioApp = {
val zioHttpServerApp = for {
allSchemaRegistryEndpoints <- SchemaRegistryServerEndpoints.all
allEndpoints = ZHttpEndpoints.withDocumentations[Task](allSchemaRegistryEndpoints)
appConfig <- ZIO.service[AppConfig]
httpServer <- ZHttp4sBlazeServer.start(allEndpoints, port = appConfig.agent.httpEndpoint.http.port)
} yield httpServer

zioHttpServerApp
.provideLayer(SchemaRegistryServiceInMemory.layer ++ SystemModule.configLayer)
.unit
}

def didCommServiceEndpoint(port: Int) = {
val header = "content-type" -> MediaTypes.contentTypeEncrypted
val app: HttpApp[DidComm with CredentialService, Throwable] =
Expand Down Expand Up @@ -209,7 +225,6 @@ object Modules {
}

}

object SystemModule {
val actorSystemLayer: TaskLayer[ActorSystem[Nothing]] = ZLayer.scoped(
ZIO.acquireRelease(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import zio.config.magnolia.Descriptor
final case class AppConfig(
iris: IrisConfig,
castor: CastorConfig,
pollux: PolluxConfig
pollux: PolluxConfig,
agent: AgentConfig
)

object AppConfig {
Expand All @@ -21,3 +22,9 @@ final case class PolluxConfig(database: DatabaseConfig)
final case class GrpcServiceConfig(host: String, port: Int)

final case class DatabaseConfig(host: String, port: Int, databaseName: String, username: String, password: String)

final case class AgentConfig(httpEndpoint: HttpEndpointConfig)

final case class HttpEndpointConfig(http: HttpConfig)

final case class HttpConfig(port: Int)
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package io.iohk.atala.agent.server.http

import zio.{Task, ZIO, ZLayer, URIO}
import io.iohk.atala.pollux.schema.SchemaRegistryServerEndpoints
import io.iohk.atala.pollux.service.SchemaRegistryService
import sttp.tapir.redoc.bundle.RedocInterpreter
import sttp.tapir.swagger.bundle.SwaggerInterpreter
//import sttp.tapir.ztapir.{RichZServerEndpoint, ZServerEndpoint}
import sttp.tapir.redoc.RedocUIOptions
import sttp.tapir.server.ServerEndpoint
import scala.concurrent.Future

object ZHttpEndpoints {
def swaggerEndpoints[F[_]](apiEndpoints: List[ServerEndpoint[Any, F]]): List[ServerEndpoint[Any, F]] =
SwaggerInterpreter().fromServerEndpoints[F](apiEndpoints, "Prism Agent", "1.0.0")

def redocEndpoints[F[_]](apiEndpoints: List[ServerEndpoint[Any, F]]): List[ServerEndpoint[Any, F]] =
RedocInterpreter(redocUIOptions = RedocUIOptions.default.copy(pathPrefix = List("redoc")))
.fromServerEndpoints[F](apiEndpoints, title = "Prism Agent", version = "1.0.0")

def withDocumentations[F[_]](apiEndpoints: List[ServerEndpoint[Any, F]]): List[ServerEndpoint[Any, F]] = {
apiEndpoints ++ swaggerEndpoints[F](apiEndpoints) ++ redocEndpoints[F](apiEndpoints)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package io.iohk.atala.agent.server.http

import cats.implicits.*
import io.iohk.atala.agent.server.http.ZHttpEndpoints
import io.iohk.atala.pollux.schema.SchemaRegistryServerEndpoints
import io.iohk.atala.pollux.service.{SchemaRegistryService, SchemaRegistryServiceInMemory}
import org.http4s.*
import org.http4s.blaze.server.BlazeServerBuilder
import org.http4s.server.Router
import org.slf4j.LoggerFactory
import sttp.model.StatusCode
import sttp.tapir.server.ServerEndpoint
import sttp.tapir.server.http4s.ztapir.ZHttp4sServerInterpreter
import sttp.tapir.server.interceptor.log.DefaultServerLog
import sttp.tapir.ztapir.ZServerEndpoint
import zhttp.http.HttpApp
import zhttp.service.server.ServerChannelFactory
import zhttp.service.{EventLoopGroup, Server}
import zio.interop.catz.*
import zio.*

import scala.concurrent.ExecutionContext.Implicits.global

object ZHttp4sBlazeServer {
def start(
endpoints: List[ZServerEndpoint[Any, Any]],
port: Int
): Task[ExitCode] = {
val http4sEndpoints: HttpRoutes[Task] =
ZHttp4sServerInterpreter().from(endpoints).toRoutes

val serve: Task[Unit] =
ZIO.executor.flatMap(executor =>
BlazeServerBuilder[Task]
.withExecutionContext(executor.asExecutionContext)
.bindHttp(port, "0.0.0.0")
.withHttpApp(Router("/" -> http4sEndpoints).orNotFound)
.serve
.compile
.drain
)

serve.exitCode
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package io.iohk.atala.api.http

import sttp.model.StatusCode
import sttp.tapir.EndpointOutput.OneOf
import sttp.tapir.generic.auto.*
import sttp.tapir.json.zio.jsonBody
import zio.json.{DeriveJsonDecoder, DeriveJsonEncoder}

sealed trait FailureResponse

case class NotFoundResponse(msg: String) extends FailureResponse

object NotFoundResponse {
given encoder: zio.json.JsonEncoder[NotFoundResponse] = DeriveJsonEncoder.gen[NotFoundResponse]
given decoder: zio.json.JsonDecoder[NotFoundResponse] = DeriveJsonDecoder.gen[NotFoundResponse]
}

case class BadRequest(msg: String, errors: List[String] = List.empty) extends FailureResponse

object BadRequest {
given encoder: zio.json.JsonEncoder[BadRequest] = DeriveJsonEncoder.gen[BadRequest]
given decoder: zio.json.JsonDecoder[BadRequest] = DeriveJsonDecoder.gen[BadRequest]
}

case class InternalServerError(msg: String) extends FailureResponse

object InternalServerError {
given encoder: zio.json.JsonEncoder[InternalServerError] = DeriveJsonEncoder.gen[InternalServerError]
given decoder: zio.json.JsonDecoder[InternalServerError] = DeriveJsonDecoder.gen[InternalServerError]
}

//An RFC-7807 compliant data structure for reporting errors to the client
case class ErrorResponse(`type`: String, title: String, status: Int, instance: String, details: Option[String])
extends FailureResponse

object ErrorResponse {
given encoder: zio.json.JsonEncoder[ErrorResponse] = DeriveJsonEncoder.gen[ErrorResponse]
given decoder: zio.json.JsonDecoder[ErrorResponse] = DeriveJsonDecoder.gen[ErrorResponse]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package io.iohk.atala.pollux.schema

import zio.json.{DeriveJsonDecoder, DeriveJsonEncoder}

import java.time.ZonedDateTime
import java.util.UUID

case class VerifiableCredentialsSchemaInput(
id: Option[UUID],
name: String,
version: String,
description: Option[String],
attributes: List[String],
authored: Option[ZonedDateTime],
tags: List[String]
)
object VerifiableCredentialsSchemaInput {
given encoder: zio.json.JsonEncoder[VerifiableCredentialsSchemaInput] =
DeriveJsonEncoder.gen[VerifiableCredentialsSchemaInput]
given decoder: zio.json.JsonDecoder[VerifiableCredentialsSchemaInput] =
DeriveJsonDecoder.gen[VerifiableCredentialsSchemaInput]
}

case class VerifiableCredentialsSchema(
id: UUID,
name: String,
version: String,
tags: List[String],
description: Option[String],
attributes: List[String],
author: String,
authored: ZonedDateTime,
proof: Option[Proof]
)

object VerifiableCredentialsSchema {
def apply(in: VerifiableCredentialsSchemaInput): VerifiableCredentialsSchema =
VerifiableCredentialsSchema(
id = in.id.getOrElse(UUID.randomUUID()),
name = in.name,
version = in.version,
tags = in.tags,
description = in.description,
attributes = in.attributes,
author = "Prism Agent",
authored = in.authored.getOrElse(ZonedDateTime.now()),
proof = None
)

given encoder: zio.json.JsonEncoder[VerifiableCredentialsSchema] = DeriveJsonEncoder.gen[VerifiableCredentialsSchema]
given decoder: zio.json.JsonDecoder[VerifiableCredentialsSchema] = DeriveJsonDecoder.gen[VerifiableCredentialsSchema]
}

case class Proof(
`type`: String,
created: ZonedDateTime,
verificationMethod: String,
proofPurpose: String,
proofValue: String,
domain: Option[String]
)

object Proof {
given encoder: zio.json.JsonEncoder[Proof] = DeriveJsonEncoder.gen[Proof]
given decoder: zio.json.JsonDecoder[Proof] = DeriveJsonDecoder.gen[Proof]
}

0 comments on commit b3cf828

Please sign in to comment.