Skip to content

Commit

Permalink
feat: Configurations load improvement
Browse files Browse the repository at this point in the history
Validations is done when zlayer is created
Add logs to configurations
More type safe
Major update of the config library
Add tests
Improvements for #938

Signed-off-by: FabioPinheiro <fabiomgpinheiro@gmail.com>
  • Loading branch information
FabioPinheiro committed Apr 1, 2024
1 parent 43a2e7f commit cf968fc
Show file tree
Hide file tree
Showing 12 changed files with 92 additions and 86 deletions.
4 changes: 3 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ lazy val V = new {

// https://mvnrepository.com/artifact/dev.zio/zio
val zio = "2.0.18"
val zioConfig = "3.0.7"
val zioConfig = "4.0.1"
val zioLogging = "2.1.16"
val zioJson = "0.3.0"
val zioHttp = "3.0.0-RC4"
Expand Down Expand Up @@ -112,6 +112,7 @@ lazy val D = new {
val zioConfig: ModuleID = "dev.zio" %% "zio-config" % V.zioConfig
val zioConfigMagnolia: ModuleID = "dev.zio" %% "zio-config-magnolia" % V.zioConfig
val zioConfigTypesafe: ModuleID = "dev.zio" %% "zio-config-typesafe" % V.zioConfig
val zioConfigRefined: ModuleID = "dev.zio" %% "zio-config-refined" % V.zioConfig

val circeCore: ModuleID = "io.circe" %% "circe-core" % V.circe
val circeGeneric: ModuleID = "io.circe" %% "circe-generic" % V.circe
Expand Down Expand Up @@ -381,6 +382,7 @@ lazy val D_PrismAgent = new {
D.zioConfig,
D.zioConfigMagnolia,
D.zioConfigTypesafe,
D.zioConfigRefined,
D.zioJson,
logback,
D.zioHttp,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,20 +43,21 @@ import io.iohk.atala.prism.protos.node_api.NodeServiceGrpc
import io.iohk.atala.shared.db.{ContextAwareTask, DbConfig, TransactorLayer}
import org.keycloak.authorization.client.AuthzClient
import zio.*
import zio.config.typesafe.TypesafeConfigSource
import zio.config.{ReadError, read}
import zio.config.typesafe.TypesafeConfigProvider
import zio.http.Client

object SystemModule {
val configLayer: Layer[ReadError[String], AppConfig] = ZLayer.fromZIO {
read(
AppConfig.descriptor.from(
TypesafeConfigSource.fromTypesafeConfig(
ZIO.attempt(ConfigFactory.load())
)
)
)
}

private val tmp: IO[Config.Error, AppConfig] =
for {
ret: AppConfig <- TypesafeConfigProvider
.fromTypesafeConfig(ConfigFactory.load())
.load(AppConfig.config)
_ <- ZIO.log(s"HTTP server endpoint is setup as '${ret.agent.httpEndpoint.publicEndpointUrl}'")
_ <- ZIO.log(s"DIDComm server endpoint is setup as '${ret.agent.didCommEndpoint.publicEndpointUrl}'")
} yield ret

val configLayer = ZLayer.fromZIO(tmp)

val zioHttpClientLayer: ZLayer[Any, Throwable, Client] = {
import zio.http.netty.NettyConfig
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import io.iohk.atala.iam.entity.http.EntityServerEndpoints
import io.iohk.atala.iam.wallet.http.WalletManagementServerEndpoints
import io.iohk.atala.issue.controller.IssueServerEndpoints
import io.iohk.atala.mercury.{DidOps, HttpClient}
import io.iohk.atala.pollux.core.service.{CredentialService, CredentialStatusListService, PresentationService}
import io.iohk.atala.pollux.core.service.{CredentialService, PresentationService}
import io.iohk.atala.pollux.credentialdefinition.CredentialDefinitionRegistryServerEndpoints
import io.iohk.atala.pollux.credentialschema.{SchemaRegistryServerEndpoints, VerificationPolicyServerEndpoints}
import io.iohk.atala.pollux.vc.jwt.DidResolver as JwtDidResolver
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import io.iohk.atala.castor.core.model.did.VerificationRelationship
import io.iohk.atala.iam.authentication.AuthenticationConfig
import io.iohk.atala.pollux.vc.jwt.*
import io.iohk.atala.shared.db.DbConfig
import zio.Config
import zio.config.*
import zio.config.magnolia.Descriptor
import zio.config.magnolia.*
import java.net.URL
import java.time.Duration
import scala.util.Try
Expand All @@ -23,7 +24,18 @@ final case class AppConfig(
}

object AppConfig {
val descriptor: ConfigDescriptor[AppConfig] = Descriptor[AppConfig]
given Config[java.net.URL] = Config.string.mapOrFail(url =>
val urlRegex = """^(http|https)://[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*(:[0-9]{1,5})?(/.*)?$""".r
urlRegex.findFirstMatchIn(url) match
case Some(_) =>
Try(java.net.URL(url)).toEither.left.map(ex /*java.net.MalformedURLException*/ =>
Config.Error.InvalidData(zio.Chunk.empty, ex.getMessage())
)
case _ => Left(Config.Error.InvalidData(zio.Chunk.empty, s"Invalid URL: $url"))
)

val config: Config[AppConfig] = deriveConfig[AppConfig]

}

final case class VaultConfig(
Expand Down Expand Up @@ -77,9 +89,7 @@ final case class PrismNodeConfig(service: GrpcServiceConfig)

final case class GrpcServiceConfig(host: String, port: Int, usePlainText: Boolean)

final case class StatusListRegistryConfig(
publicEndpointUrl: String
)
final case class StatusListRegistryConfig(publicEndpointUrl: java.net.URL)

final case class DatabaseConfig(
host: String,
Expand Down Expand Up @@ -169,28 +179,13 @@ final case class AgentConfig(
"The default wallet must be enabled if all the authentication methods are disabled. Default wallet is required for the single-tenant mode."
)
_ <- secretStorage.validate
_ <- httpEndpoint.validate
_ <- didCommEndpoint.validate
} yield ()

}

object EndpointConfig {
def validate(url: String): Either[String, Unit] = {
val urlRegex = """^(http|https)://[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*(:[0-9]{1,5})?(/.*)?$""".r
urlRegex.findFirstMatchIn(url) match
case Some(_) => Right(())
case _ => Left(s"Invalid URL: $url")
}
}

final case class HttpEndpointConfig(http: HttpConfig, publicEndpointUrl: String) {
def validate: Either[String, Unit] = EndpointConfig.validate(publicEndpointUrl)
}
final case class HttpEndpointConfig(http: HttpConfig, publicEndpointUrl: java.net.URL)

final case class DidCommEndpointConfig(http: HttpConfig, publicEndpointUrl: String) {
def validate: Either[String, Unit] = EndpointConfig.validate(publicEndpointUrl)
}
final case class DidCommEndpointConfig(http: HttpConfig, publicEndpointUrl: java.net.URL)

final case class HttpConfig(port: Int)

Expand All @@ -216,13 +211,13 @@ enum SecretStorageBackend {
}

object SecretStorageBackend {
given Descriptor[SecretStorageBackend] =
Descriptor.from(
Descriptor[String].transformOrFailLeft { s =>
Try(SecretStorageBackend.valueOf(s)).toOption
.toRight(
s"Invalid configuration value '$s'. Possible values: ${SecretStorageBackend.values.mkString("[", ", ", "]")}"
)
}(_.toString())
)
// given Descriptor[SecretStorageBackend] =
// Descriptor.from(
// Descriptor[String].transformOrFailLeft { s =>
// Try(SecretStorageBackend.valueOf(s)).toOption
// .toRight(
// s"Invalid configuration value '$s'. Possible values: ${SecretStorageBackend.values.mkString("[", ", ", "]")}"
// )
// }(_.toString())
// )
}
Original file line number Diff line number Diff line change
Expand Up @@ -408,7 +408,7 @@ object IssueBackgroundJobs extends BackgroundJobsHelper {
credentialService <- ZIO.service[CredentialService]
config <- ZIO.service[AppConfig]
_ <- credentialService
.generateJWTCredential(id, config.pollux.statusListRegistry.publicEndpointUrl)
.generateJWTCredential(id, config.pollux.statusListRegistry.publicEndpointUrl.toExternalForm)
.provideSomeLayer(ZLayer.succeed(walletAccessContext))
} yield ()).mapError(e => (walletAccessContext, e))
} yield result
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ class IssueControllerImpl(
ErrorResponse.badRequest(detail = Some("Missing request parameter: credentialDefinitionId"))
)
credentialDefinitionId = {
val publicEndpointUrl = appConfig.agent.httpEndpoint.publicEndpointUrl
val publicEndpointUrl = appConfig.agent.httpEndpoint.publicEndpointUrl.toExternalForm
val urlSuffix =
s"credential-definition-registry/definitions/${credentialDefinitionGUID.toString}/definition"
val urlPrefix = if (publicEndpointUrl.endsWith("/")) publicEndpointUrl else publicEndpointUrl + "/"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import zio.test.*
import zio.test.Assertion.*
import zio.test.ZIOSpecDefault

/** prismAgentServer/testOnly io.iohk.atala.agent.server.config.AppConfigSpec */
object AppConfigSpec extends ZIOSpecDefault {

private val baseVaultConfig = VaultConfig(
Expand All @@ -16,7 +17,7 @@ object AppConfigSpec extends ZIOSpecDefault {
appRoleSecretId = None,
useSemanticPath = true,
)
private val baseInvalidHttpEndpointConfig = "http://:8080"
// private val baseInvalidHttpEndpointConfig = java.net.URL("http://:8080")

override def spec = suite("AppConfigSpec")(
test("load config successfully") {
Expand Down Expand Up @@ -75,26 +76,36 @@ object AppConfigSpec extends ZIOSpecDefault {
)
assert(vaultConfig.validate)(isRight(isSubtype[ValidatedVaultConfig.TokenAuth](anything)))
},
test("reject config when invalid http endpoint config provided") {
for {
appConfig <- ZIO.serviceWith[AppConfig](
_.focus(_.agent.secretStorage.backend)
.replace(SecretStorageBackend.memory)
.focus(_.agent.httpEndpoint.publicEndpointUrl)
.replace(baseInvalidHttpEndpointConfig)
)
} yield assert(appConfig.validate)(isLeft(containsString("Invalid URL: http://:8080")))
},
test("reject config when invalid http didcomm service endpoint url provided") {
for {
appConfig <- ZIO.serviceWith[AppConfig](
_.focus(_.agent.secretStorage.backend)
.replace(SecretStorageBackend.memory)
.focus(_.agent.didCommEndpoint.publicEndpointUrl)
.replace(baseInvalidHttpEndpointConfig)
).provide(SystemModule.configLayer) + {

import AppConfig.given
import zio.config.magnolia.*
val didCommEndpointConfig: Config[DidCommEndpointConfig] = deriveConfig[DidCommEndpointConfig]

suite("DidCommEndpointConfig URL type")(
test("DidCommEndpointConfig that the correct format") {
{
for {
didCommEndpointConfig <- ZIO.service[DidCommEndpointConfig]
} yield assert(true)(isTrue)
}.provide(
ZLayer.fromZIO(
ConfigProvider
.fromMap(Map("http.port" -> "9999", "publicEndpointUrl" -> "http://example:8080/path"))
.load(didCommEndpointConfig)
)
)
} yield assert(appConfig.validate)(isLeft(containsString("Invalid URL: http://:8080")))
},
).provide(SystemModule.configLayer)
},
test("reject config when invalid http didcomm service endpoint url provided") {

assertZIO(
ConfigProvider
.fromMap(Map("http.port" -> "9999", "publicEndpointUrl" -> "http://:8080/path"))
.load(didCommEndpointConfig)
.exit
)(fails(isSubtype[Config.Error.InvalidData](anything)))
// Config.Error.InvalidData(zio.Chunk("publicEndpointUrl"), "Invalid URL: http://:8080/path")
},
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,7 @@ import sttp.tapir.server.interceptor.CustomiseInterceptors
import sttp.tapir.server.stub.TapirStubInterpreter
import sttp.tapir.ztapir.RIOMonadError
import zio.*
import zio.config.typesafe.TypesafeConfigSource
import zio.config.{ReadError, read}
import zio.config.typesafe.TypesafeConfigProvider
import zio.json.ast.Json
import zio.json.ast.Json.*
import zio.test.*
Expand All @@ -54,15 +53,11 @@ trait IssueControllerTestTools extends PostgresTestContainerSupport {
]
val didResolverLayer = ZLayer.fromZIO(ZIO.succeed(makeResolver(Map.empty)))

val configLayer: Layer[ReadError[String], AppConfig] = ZLayer.fromZIO {
read(
AppConfig.descriptor.from(
TypesafeConfigSource.fromTypesafeConfig(
ZIO.attempt(ConfigFactory.load())
)
)
)
}
val configLayer = ZLayer.fromZIO(
TypesafeConfigProvider
.fromTypesafeConfig(ConfigFactory.load())
.load(AppConfig.config)
)

private[this] def makeResolver(lookup: Map[String, DIDDocument]): DidResolver = (didUrl: String) => {
lookup
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ trait ManagedDIDService {
): ZIO[WalletAccessContext, UpdateManagedDIDError, ScheduleDIDOperationOutcome]

/** PeerDID related methods */
def createAndStorePeerDID(serviceEndpoint: String): URIO[WalletAccessContext, PeerDID]
def createAndStorePeerDID(serviceEndpoint: java.net.URL): URIO[WalletAccessContext, PeerDID]

def getPeerDID(didId: DidId): ZIO[WalletAccessContext, DIDSecretStorageError.KeyNotFoundError, PeerDID]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -345,9 +345,9 @@ class ManagedDIDServiceImpl private[walletapi] (

/** PeerDID related methods
*/
def createAndStorePeerDID(serviceEndpoint: String): URIO[WalletAccessContext, PeerDID] =
def createAndStorePeerDID(serviceEndpoint: java.net.URL): URIO[WalletAccessContext, PeerDID] =
for {
peerDID <- ZIO.succeed(PeerDID.makePeerDid(serviceEndpoint = Some(serviceEndpoint)))
peerDID <- ZIO.succeed(PeerDID.makePeerDid(serviceEndpoint = Some(serviceEndpoint.toExternalForm())))
_ <- nonSecretStorage.createPeerDIDRecord(peerDID.did).orDie
_ <- secretStorage.insertKey(peerDID.did, AGREEMENT_KEY_ID, peerDID.jwkForKeyAgreement).orDie
_ <- secretStorage.insertKey(peerDID.did, AUTHENTICATION_KEY_ID, peerDID.jwkForKeyAuthentication).orDie
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ object MockManagedDIDService extends Mock[ManagedDIDService] {
): IO[UpdateManagedDIDError, ScheduleDIDOperationOutcome] = ???

override def createAndStorePeerDID(
serviceEndpoint: String
serviceEndpoint: java.net.URL
): UIO[PeerDID] = ???

override def getPeerDID(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -320,8 +320,9 @@ object ManagedDIDServiceSpec
ctx2 = ZLayer.succeed(WalletAccessContext(walletId2))
svc <- ZIO.service[ManagedDIDService]
storage <- ZIO.service[DIDNonSecretStorage]
peerDid1 <- svc.createAndStorePeerDID("http://example.com").provide(ctx1)
peerDid2 <- svc.createAndStorePeerDID("http://example.com").provide(ctx2)
urlTmp = java.net.URL("http://example.com")
peerDid1 <- svc.createAndStorePeerDID(urlTmp).provide(ctx1)
peerDid2 <- svc.createAndStorePeerDID(urlTmp).provide(ctx2)
record1 <- storage.getPeerDIDRecord(peerDid1.did)
record2 <- storage.getPeerDIDRecord(peerDid2.did)
} yield {
Expand Down Expand Up @@ -514,8 +515,9 @@ object ManagedDIDServiceSpec
ctx1 = ZLayer.succeed(WalletAccessContext(walletId1))
ctx2 = ZLayer.succeed(WalletAccessContext(walletId2))
svc <- ZIO.service[ManagedDIDService]
dids1 <- ZIO.foreach(1 to 3)(_ => svc.createAndStorePeerDID("http://example.com")).provide(ctx1)
dids2 <- ZIO.foreach(1 to 3)(_ => svc.createAndStorePeerDID("http://example.com")).provide(ctx2)
urlTmp = java.net.URL("http://example.com")
dids1 <- ZIO.foreach(1 to 3)(_ => svc.createAndStorePeerDID(urlTmp)).provide(ctx1)
dids2 <- ZIO.foreach(1 to 3)(_ => svc.createAndStorePeerDID(urlTmp)).provide(ctx2)
ownWalletDids1 <- ZIO.foreach(dids1)(d => svc.getPeerDID(d.did).exit).provide(ctx1)
ownWalletDids2 <- ZIO.foreach(dids2)(d => svc.getPeerDID(d.did).exit).provide(ctx2)
crossWalletDids1 <- ZIO.foreach(dids1)(d => svc.getPeerDID(d.did).exit).provide(ctx2)
Expand Down

0 comments on commit cf968fc

Please sign in to comment.