From d8ed74ce4e84faf694346068daa1e6ef51f8cd9b Mon Sep 17 00:00:00 2001 From: alexcardell <29524087+alexcardell@users.noreply.github.com> Date: Fri, 6 Sep 2024 15:20:12 +0100 Subject: [PATCH] Implement Flipt OpenFeature Provider boolean evaluation --- .sbtopts | 2 + .scalafix-base.conf | 35 -- .scalafix.conf | 28 +- .scalafmt.conf | 2 + build.sbt | 178 +++++--- .../ff4s/flipt/FliptApiImplItTest.scala | 15 +- .../io/cardell/ff4s/flipt/FliptApi.scala | 16 +- .../io/cardell/ff4s/flipt/FliptApiImpl.scala | 19 +- .../ff4s/flipt/auth/AuthMiddleware.scala | 23 +- .../flipt/auth/AuthenticationStrategy.scala | 2 +- .../flipt/model/BatchEvaluationRequest.scala | 5 +- .../flipt/model/ErrorEvaluationReason.scala | 6 +- .../ff4s/flipt/model/EvaluationReason.scala | 16 +- .../ff4s/flipt/model/EvaluationRequest.scala | 2 +- .../ff4s/flipt/model/EvaluationResponse.scala | 2 + .../model/StructuredEvaluationResponse.scala | 17 +- .../provider/flipt/FliptProviderItTest.scala | 186 ++++++++ .../provider/flipt/FliptProvider.scala | 100 ++-- .../io/cardell/openfeature/ErrorCode.scala | 17 +- .../openfeature/EvaluationContext.scala | 55 ++- .../openfeature/EvaluationDetails.scala | 36 +- .../openfeature/EvaluationReason.scala | 16 + .../cardell/openfeature/FeatureClient.scala | 253 ++++++----- .../openfeature/FeatureClientImpl.scala | 429 +++++++++--------- .../io/cardell/openfeature/OpenFeature.scala | 21 +- .../io/cardell/openfeature/package.scala | 20 +- .../openfeature/provider/Provider.scala | 64 ++- .../provider/ProviderMetadata.scala | 16 + .../openfeature/provider/ProviderStatus.scala | 17 +- .../provider/ResolutionDetails.scala | 24 +- .../openfeature/provider/package.scala | 18 +- project/plugins.sbt | 10 +- 32 files changed, 1053 insertions(+), 597 deletions(-) create mode 100644 .sbtopts delete mode 100644 .scalafix-base.conf create mode 100644 open-feature/provider-flipt-it/src/test/scala/io/cardell/openfeature/provider/flipt/FliptProviderItTest.scala diff --git a/.sbtopts b/.sbtopts new file mode 100644 index 0000000..a17552d --- /dev/null +++ b/.sbtopts @@ -0,0 +1,2 @@ +-J-Xmx4G +-J-Xss2M diff --git a/.scalafix-base.conf b/.scalafix-base.conf deleted file mode 100644 index cce9476..0000000 --- a/.scalafix-base.conf +++ /dev/null @@ -1,35 +0,0 @@ -# run when explicitly called -rules = [ - DisableSyntax - NoAutoTupling - NoValInForComprehension - ProcedureSyntax - RedundantSyntax - OrganizeImports - RemoveUnused -] - -# run on compile (not explicitly) -# doesn't organize imports -triggered.rules = [ - DisableSyntax - NoAutoTupling - NoValInForComprehension - ProcedureSyntax - RedundantSyntax -] - -# clashes with OrganizeImports -RemoveUnused.imports = false - -OrganizeImports { - blankLines = Auto - groups = [ - "re:javax?\\." - "re:scala\\." - "re:^(?!(io.cardell)).*$" # anything except mine - "*" - ] - expandRelative = true - removeUnused = true -} diff --git a/.scalafix.conf b/.scalafix.conf index fb8030a..a741a62 100644 --- a/.scalafix.conf +++ b/.scalafix.conf @@ -5,8 +5,8 @@ rules = [ NoValInForComprehension # ProcedureSyntax RedundantSyntax -# OrganizeImports -# RemoveUnused + OrganizeImports + RemoveUnused ] # run on compile (not explicitly) @@ -20,16 +20,16 @@ triggered.rules = [ ] # clashes with OrganizeImports -# RemoveUnused.imports = false +RemoveUnused.imports = false -# OrganizeImports { -# blankLines = Auto -# groups = [ -# "re:javax?\\." -# "re:scala\\." -# "re:^(?!(io.cardell)).*$" # anything except mine -# "*" -# ] -# expandRelative = true -# removeUnused = true -# } +OrganizeImports { + blankLines = Auto + groups = [ + "re:javax?\\." + "re:scala\\." + "re:^(?!(io.cardell)).*$" # anything except mine + "*" + ] + expandRelative = true + removeUnused = true +} diff --git a/.scalafmt.conf b/.scalafmt.conf index 450ce94..94e67d3 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -1,2 +1,4 @@ version = 3.7.1 +include ".scalafmt-base.conf" + runner.dialect = scala3 diff --git a/build.sbt b/build.sbt index 2330dcc..fc45c4c 100644 --- a/build.sbt +++ b/build.sbt @@ -1,10 +1,11 @@ // https://typelevel.org/sbt-typelevel/faq.html#what-is-a-base-version-anyway ThisBuild / tlBaseVersion := "0.0" // your current series x.y -ThisBuild / organization := "io.cardell" +ThisBuild / organization := "io.cardell" ThisBuild / organizationName := "Alex Cardell" -ThisBuild / startYear := Some(2023) -ThisBuild / licenses := Seq(License.Apache2) +ThisBuild / startYear := Some(2023) +ThisBuild / licenses := Seq(License.Apache2) + ThisBuild / developers := List( // your GitHub handle and name tlGitHubDev("alexcardell", "Alex Cardell") @@ -15,99 +16,128 @@ ThisBuild / tlSonatypeUseLegacyHost := false // publish website from this branch ThisBuild / tlSitePublishBranch := Some("main") -ThisBuild / tlSiteKeepFiles := false +ThisBuild / tlSiteKeepFiles := false val Scala213 = "2.13.12" -val Scala33 = "3.3.3" +val Scala33 = "3.3.4-RC1" ThisBuild / crossScalaVersions := Seq(Scala213, Scala33) -ThisBuild / scalaVersion := Scala213 // the default Scala +ThisBuild / scalaVersion := Scala213 // the default Scala + +// hack until integration tests can run in parallel +// ThisBuild / Test / parallelExecution := false +Global / concurrentRestrictions += Tags.limit(Tags.Test, 1) lazy val projects = Seq( `flipt-sdk-server`, `flipt-sdk-server-it`, `open-feature-sdk`, - `open-feature-provider-flipt` + `open-feature-provider-flipt`, + `open-feature-provider-flipt-it` ) lazy val commonDependencies = Seq( libraryDependencies ++= Seq( - "org.typelevel" %%% "cats-core" % "2.10.0", - "org.typelevel" %%% "cats-effect" % "3.5.3", - "org.scalameta" %%% "munit" % "1.0.0-RC1" % Test, - "org.typelevel" %%% "munit-cats-effect" % "2.0.0-M5" % Test + "org.typelevel" %%% "cats-core" % "2.10.0", + "org.typelevel" %%% "cats-effect" % "3.5.3", + "org.scalameta" %%% "munit" % "1.0.0-RC1" % Test, + "org.typelevel" %%% "munit-cats-effect" % "2.0.0-M5" % Test ) ) lazy val root = tlCrossRootProject.aggregate(projects: _*) -lazy val `flipt-sdk-server` = - crossProject(JVMPlatform, JSPlatform, NativePlatform) - .crossType(CrossType.Full) - .in(file("flipt/sdk-server")) - .settings(commonDependencies) - .settings( - name := "ff4s-flipt-sdk-server", - libraryDependencies ++= Seq( - "org.http4s" %%% "http4s-client" % "0.23.26", - "org.http4s" %%% "http4s-ember-client" % "0.23.26", - "org.http4s" %%% "http4s-circe" % "0.23.26", - "io.circe" %%% "circe-core" % "0.14.7", - "io.circe" %%% "circe-parser" % "0.14.7", - "io.circe" %%% "circe-generic" % "0.14.7" - ) +lazy val `flipt-sdk-server` = crossProject( + JVMPlatform, + JSPlatform, + NativePlatform +) + .crossType(CrossType.Full) + .in(file("flipt/sdk-server")) + .settings(commonDependencies) + .settings( + name := "ff4s-flipt-sdk-server", + libraryDependencies ++= Seq( + "org.http4s" %%% "http4s-client" % "0.23.26", + "org.http4s" %%% "http4s-ember-client" % "0.23.26", + "org.http4s" %%% "http4s-circe" % "0.23.26", + "io.circe" %%% "circe-core" % "0.14.7", + "io.circe" %%% "circe-parser" % "0.14.7", + "io.circe" %%% "circe-generic" % "0.14.7" ) + ) -lazy val `flipt-sdk-server-it` = - crossProject(JVMPlatform) - .crossType(CrossType.Pure) - .in(file("flipt/sdk-server-it")) - .dependsOn(`flipt-sdk-server`) - .settings(commonDependencies) - .settings( - libraryDependencies ++= Seq( - "com.dimafeng" %% "testcontainers-scala-munit" % "0.41.3" % Test - ) +lazy val `flipt-sdk-server-it` = crossProject(JVMPlatform) + .crossType(CrossType.Pure) + .in(file("flipt/sdk-server-it")) + .settings(commonDependencies) + .settings( + libraryDependencies ++= Seq( + "com.dimafeng" %% "testcontainers-scala-munit" % "0.41.3" % Test ) + ) + .dependsOn(`flipt-sdk-server`) -lazy val `open-feature-sdk` = - crossProject(JVMPlatform, JSPlatform, NativePlatform) - .crossType(CrossType.Pure) - .in(file("open-feature/sdk")) - .enablePlugins(NoPublishPlugin) - .settings(commonDependencies) - .settings( - name := "ff4s-open-feature-sdk", - libraryDependencies ++= Seq( - "io.circe" %%% "circe-generic" % "0.14.7" - ) +lazy val `open-feature-sdk` = crossProject( + JVMPlatform, + JSPlatform, + NativePlatform +) + .crossType(CrossType.Pure) + .in(file("open-feature/sdk")) + .enablePlugins(NoPublishPlugin) + .settings(commonDependencies) + .settings( + name := "ff4s-open-feature-sdk", + libraryDependencies ++= Seq( + "io.circe" %%% "circe-generic" % "0.14.7" ) + ) -lazy val `open-feature-provider-flipt` = - crossProject(JVMPlatform, JSPlatform, NativePlatform) - .crossType(CrossType.Pure) - .in(file("open-feature/provider-flipt")) - .enablePlugins(NoPublishPlugin) - .settings(commonDependencies) - .settings( - name := "ff4s-open-feature-provider-flipt" - ) - .dependsOn( - `open-feature-sdk`, - `flipt-sdk-server` - ) +lazy val `open-feature-provider-flipt` = crossProject( + JVMPlatform, + JSPlatform, + NativePlatform +) + .crossType(CrossType.Pure) + .in(file("open-feature/provider-flipt")) + .enablePlugins(NoPublishPlugin) + .settings(commonDependencies) + .settings( + name := "ff4s-open-feature-provider-flipt" + ) + .dependsOn( + `open-feature-sdk`, + `flipt-sdk-server` + ) -lazy val docs = - project - .in(file("site")) - .enablePlugins(TypelevelSitePlugin) - .settings( - tlSiteHelium := { - import laika.helium.config.IconLink - import laika.helium.config.HeliumIcon - import laika.ast.Path.Root - tlSiteHelium.value.site.topNavigationBar( - homeLink = IconLink.internal(Root / "index.md", HeliumIcon.home) - ) - } +lazy val `open-feature-provider-flipt-it` = crossProject(JVMPlatform) + .crossType(CrossType.Pure) + .in(file("open-feature/provider-flipt-it")) + .enablePlugins(NoPublishPlugin) + .settings(commonDependencies) + .settings( + name := "ff4s-open-feature-provider-flipt-it", + libraryDependencies ++= Seq( + "com.dimafeng" %% "testcontainers-scala-munit" % "0.41.3" % Test ) - .dependsOn(`flipt-sdk-server`.jvm) + ) + .dependsOn( + `open-feature-provider-flipt` + ) + +lazy val docs = project + .in(file("site")) + .enablePlugins(TypelevelSitePlugin) + .settings( + tlSiteHelium := { + import laika.helium.config.IconLink + import laika.helium.config.HeliumIcon + import laika.ast.Path.Root + tlSiteHelium.value.site.topNavigationBar( + homeLink = IconLink.internal(Root / "index.md", HeliumIcon.home) + ) + } + ) + .dependsOn(`flipt-sdk-server`.jvm) + +addCommandAlias("fix", "scalafixAll;scalafmtAll;scalafmtSbt") diff --git a/flipt/sdk-server-it/src/test/scala/io/cardell/ff4s/flipt/FliptApiImplItTest.scala b/flipt/sdk-server-it/src/test/scala/io/cardell/ff4s/flipt/FliptApiImplItTest.scala index 8d42c27..3829260 100644 --- a/flipt/sdk-server-it/src/test/scala/io/cardell/ff4s/flipt/FliptApiImplItTest.scala +++ b/flipt/sdk-server-it/src/test/scala/io/cardell/ff4s/flipt/FliptApiImplItTest.scala @@ -22,15 +22,15 @@ import com.dimafeng.testcontainers.ContainerDef import com.dimafeng.testcontainers.DockerComposeContainer import com.dimafeng.testcontainers.ExposedService import com.dimafeng.testcontainers.munit.TestContainerForAll -import io.cardell.ff4s.flipt.auth.AuthenticationStrategy import io.circe.Decoder import io.circe.generic.semiauto.deriveDecoder +import java.io.File import munit.CatsEffectSuite import org.http4s.Uri import org.http4s.ember.client.EmberClientBuilder import org.testcontainers.containers.wait.strategy.Wait -import java.io.File +import io.cardell.ff4s.flipt.auth.AuthenticationStrategy class FliptApiImplItTest extends CatsEffectSuite with TestContainerForAll { @@ -43,10 +43,11 @@ class FliptApiImplItTest extends CatsEffectSuite with TestContainerForAll { ) def api(containers: Containers): Resource[IO, FliptApi[IO]] = { - val flipt = containers - .asInstanceOf[DockerComposeContainer] - .getContainerByServiceName("flipt") - .get + val flipt = + containers + .asInstanceOf[DockerComposeContainer] + .getContainerByServiceName("flipt") + .get val url = Uri .fromString( @@ -119,6 +120,7 @@ class FliptApiImplItTest extends CatsEffectSuite with TestContainerForAll { } case class TestVariant(field: String, intField: Int) + object TestVariant { implicit val decoder: Decoder[TestVariant] = deriveDecoder } @@ -166,4 +168,5 @@ class FliptApiImplItTest extends CatsEffectSuite with TestContainerForAll { } } } + } diff --git a/flipt/sdk-server/shared/src/main/scala/io/cardell/ff4s/flipt/FliptApi.scala b/flipt/sdk-server/shared/src/main/scala/io/cardell/ff4s/flipt/FliptApi.scala index 8832b9b..58b72da 100644 --- a/flipt/sdk-server/shared/src/main/scala/io/cardell/ff4s/flipt/FliptApi.scala +++ b/flipt/sdk-server/shared/src/main/scala/io/cardell/ff4s/flipt/FliptApi.scala @@ -17,6 +17,10 @@ package io.cardell.ff4s.flipt import cats.effect.Concurrent +import io.circe.Decoder +import org.http4s.Uri +import org.http4s.client.Client + import io.cardell.ff4s.flipt.auth.AuthMiddleware import io.cardell.ff4s.flipt.auth.AuthenticationStrategy import io.cardell.ff4s.flipt.model.AttachmentDecodingError @@ -25,14 +29,13 @@ import io.cardell.ff4s.flipt.model.BatchEvaluationResponse import io.cardell.ff4s.flipt.model.BooleanEvaluationResponse import io.cardell.ff4s.flipt.model.StructuredVariantEvaluationResponse import io.cardell.ff4s.flipt.model.VariantEvaluationResponse -import io.circe.Decoder -import org.http4s.Uri -import org.http4s.client.Client trait FliptApi[F[_]] { + def evaluateBoolean( request: EvaluationRequest ): F[BooleanEvaluationResponse] + def evaluateVariant( request: EvaluationRequest ): F[VariantEvaluationResponse] @@ -45,16 +48,19 @@ trait FliptApi[F[_]] { def evaluateStructuredVariant[A: Decoder]( request: EvaluationRequest ): F[Either[AttachmentDecodingError, StructuredVariantEvaluationResponse[A]]] + def evaluateBatch( request: BatchEvaluationRequest ): F[BatchEvaluationResponse] + } object FliptApi { + def apply[F[_]: Concurrent]( client: Client[F], uri: Uri, strategy: AuthenticationStrategy - ): FliptApi[F] = - new FliptApiImpl[F](AuthMiddleware(client, strategy), uri) + ): FliptApi[F] = new FliptApiImpl[F](AuthMiddleware(client, strategy), uri) + } diff --git a/flipt/sdk-server/shared/src/main/scala/io/cardell/ff4s/flipt/FliptApiImpl.scala b/flipt/sdk-server/shared/src/main/scala/io/cardell/ff4s/flipt/FliptApiImpl.scala index 7f6161c..1c1fbd9 100644 --- a/flipt/sdk-server/shared/src/main/scala/io/cardell/ff4s/flipt/FliptApiImpl.scala +++ b/flipt/sdk-server/shared/src/main/scala/io/cardell/ff4s/flipt/FliptApiImpl.scala @@ -17,13 +17,7 @@ package io.cardell.ff4s.flipt import cats.effect.Concurrent -import cats.syntax.all.* -import io.cardell.ff4s.flipt.model.AttachmentDecodingError -import io.cardell.ff4s.flipt.model.BatchEvaluationRequest -import io.cardell.ff4s.flipt.model.BatchEvaluationResponse -import io.cardell.ff4s.flipt.model.BooleanEvaluationResponse -import io.cardell.ff4s.flipt.model.StructuredVariantEvaluationResponse -import io.cardell.ff4s.flipt.model.VariantEvaluationResponse +import cats.syntax.all._ import io.circe.Decoder import org.http4s.Method import org.http4s.Request @@ -31,6 +25,13 @@ import org.http4s.Uri import org.http4s.circe.CirceEntityCodec._ import org.http4s.client.Client +import io.cardell.ff4s.flipt.model.AttachmentDecodingError +import io.cardell.ff4s.flipt.model.BatchEvaluationRequest +import io.cardell.ff4s.flipt.model.BatchEvaluationResponse +import io.cardell.ff4s.flipt.model.BooleanEvaluationResponse +import io.cardell.ff4s.flipt.model.StructuredVariantEvaluationResponse +import io.cardell.ff4s.flipt.model.VariantEvaluationResponse + protected[flipt] class FliptApiImpl[F[_]: Concurrent]( client: Client[F], baseUri: Uri @@ -64,9 +65,7 @@ protected[flipt] class FliptApiImpl[F[_]: Concurrent]( request: EvaluationRequest ): F[ Either[AttachmentDecodingError, StructuredVariantEvaluationResponse[A]] - ] = { - evaluateVariant(request).map(StructuredVariantEvaluationResponse[A](_)) - } + ] = evaluateVariant(request).map(StructuredVariantEvaluationResponse[A](_)) override def evaluateBatch( request: BatchEvaluationRequest diff --git a/flipt/sdk-server/shared/src/main/scala/io/cardell/ff4s/flipt/auth/AuthMiddleware.scala b/flipt/sdk-server/shared/src/main/scala/io/cardell/ff4s/flipt/auth/AuthMiddleware.scala index d8a7c68..aa2c11a 100644 --- a/flipt/sdk-server/shared/src/main/scala/io/cardell/ff4s/flipt/auth/AuthMiddleware.scala +++ b/flipt/sdk-server/shared/src/main/scala/io/cardell/ff4s/flipt/auth/AuthMiddleware.scala @@ -16,14 +16,15 @@ package io.cardell.ff4s.flipt.auth -import org.http4s.headers.Authorization import cats.effect.kernel.MonadCancelThrow -import org.typelevel.ci._ -import org.http4s.client.Client -import org.http4s.Credentials import org.http4s.AuthScheme +import org.http4s.Credentials +import org.http4s.client.Client +import org.http4s.headers.Authorization +import org.typelevel.ci._ object AuthMiddleware { + def apply[F[_]: MonadCancelThrow]( client: Client[F], strategy: AuthenticationStrategy @@ -35,13 +36,15 @@ object AuthMiddleware { private def authHeader( strategy: AuthenticationStrategy ): Authorization = { - val credentials = strategy match { - case AuthenticationStrategy.ClientToken(token) => - Credentials.Token(AuthScheme.Bearer, token) - case AuthenticationStrategy.JWT(token) => - Credentials.Token(ci"JWT", token) - } + val credentials = + strategy match { + case AuthenticationStrategy.ClientToken(token) => + Credentials.Token(AuthScheme.Bearer, token) + case AuthenticationStrategy.JWT(token) => + Credentials.Token(ci"JWT", token) + } Authorization(credentials) } + } diff --git a/flipt/sdk-server/shared/src/main/scala/io/cardell/ff4s/flipt/auth/AuthenticationStrategy.scala b/flipt/sdk-server/shared/src/main/scala/io/cardell/ff4s/flipt/auth/AuthenticationStrategy.scala index 9786cd3..555bbdb 100644 --- a/flipt/sdk-server/shared/src/main/scala/io/cardell/ff4s/flipt/auth/AuthenticationStrategy.scala +++ b/flipt/sdk-server/shared/src/main/scala/io/cardell/ff4s/flipt/auth/AuthenticationStrategy.scala @@ -20,5 +20,5 @@ sealed trait AuthenticationStrategy object AuthenticationStrategy { case class ClientToken(token: String) extends AuthenticationStrategy - case class JWT(token: String) extends AuthenticationStrategy + case class JWT(token: String) extends AuthenticationStrategy } diff --git a/flipt/sdk-server/shared/src/main/scala/io/cardell/ff4s/flipt/model/BatchEvaluationRequest.scala b/flipt/sdk-server/shared/src/main/scala/io/cardell/ff4s/flipt/model/BatchEvaluationRequest.scala index 1095e85..2ac0c7a 100644 --- a/flipt/sdk-server/shared/src/main/scala/io/cardell/ff4s/flipt/model/BatchEvaluationRequest.scala +++ b/flipt/sdk-server/shared/src/main/scala/io/cardell/ff4s/flipt/model/BatchEvaluationRequest.scala @@ -16,9 +16,10 @@ package io.cardell.ff4s.flipt.model -import io.cardell.ff4s.flipt.EvaluationRequest -import io.circe.generic.semiauto.deriveEncoder import io.circe.Encoder +import io.circe.generic.semiauto.deriveEncoder + +import io.cardell.ff4s.flipt.EvaluationRequest case class BatchEvaluationRequest( requestId: Option[String], diff --git a/flipt/sdk-server/shared/src/main/scala/io/cardell/ff4s/flipt/model/ErrorEvaluationReason.scala b/flipt/sdk-server/shared/src/main/scala/io/cardell/ff4s/flipt/model/ErrorEvaluationReason.scala index 9771dff..ad16847 100644 --- a/flipt/sdk-server/shared/src/main/scala/io/cardell/ff4s/flipt/model/ErrorEvaluationReason.scala +++ b/flipt/sdk-server/shared/src/main/scala/io/cardell/ff4s/flipt/model/ErrorEvaluationReason.scala @@ -24,7 +24,7 @@ sealed trait ErrorEvaluationReason object ErrorEvaluationReason { case object NotFound extends ErrorEvaluationReason - case object Unknown extends ErrorEvaluationReason + case object Unknown extends ErrorEvaluationReason implicit val d: Decoder[ErrorEvaluationReason] = Decoder.instance { cursor => val json = cursor.value @@ -32,8 +32,7 @@ object ErrorEvaluationReason { json.asString match { case Some(v) if v == "NOT_FOUND_ERROR_EVALUATION_REASON" => Right(NotFound) - case Some(v) if v == "UNKNOWN_ERROR_EVALUATION_REASON" => - Right(Unknown) + case Some(v) if v == "UNKNOWN_ERROR_EVALUATION_REASON" => Right(Unknown) case Some(other) => Left( DecodingFailure( @@ -47,4 +46,5 @@ object ErrorEvaluationReason { ) } } + } diff --git a/flipt/sdk-server/shared/src/main/scala/io/cardell/ff4s/flipt/model/EvaluationReason.scala b/flipt/sdk-server/shared/src/main/scala/io/cardell/ff4s/flipt/model/EvaluationReason.scala index 9418503..12fa2d1 100644 --- a/flipt/sdk-server/shared/src/main/scala/io/cardell/ff4s/flipt/model/EvaluationReason.scala +++ b/flipt/sdk-server/shared/src/main/scala/io/cardell/ff4s/flipt/model/EvaluationReason.scala @@ -23,23 +23,20 @@ import io.circe.DecodingFailure.Reason sealed trait EvaluationReason object EvaluationReason { - case object Unknown extends EvaluationReason + case object Unknown extends EvaluationReason case object FlagDisabled extends EvaluationReason - case object Match extends EvaluationReason - case object Default extends EvaluationReason + case object Match extends EvaluationReason + case object Default extends EvaluationReason implicit val d: Decoder[EvaluationReason] = Decoder.instance { cursor => val json = cursor.value json.asString match { - case Some(v) if v == "UNKNOWN_EVALUATION_REASON" => - Right(Unknown) + case Some(v) if v == "UNKNOWN_EVALUATION_REASON" => Right(Unknown) case Some(v) if v == "FLAG_DISABLED_EVALUATION_REASON" => Right(FlagDisabled) - case Some(v) if v == "MATCH_EVALUATION_REASON" => - Right(Match) - case Some(v) if v == "DEFAULT_EVALUATION_REASON" => - Right(Default) + case Some(v) if v == "MATCH_EVALUATION_REASON" => Right(Match) + case Some(v) if v == "DEFAULT_EVALUATION_REASON" => Right(Default) case Some(other) => Left( DecodingFailure( @@ -53,4 +50,5 @@ object EvaluationReason { ) } } + } diff --git a/flipt/sdk-server/shared/src/main/scala/io/cardell/ff4s/flipt/model/EvaluationRequest.scala b/flipt/sdk-server/shared/src/main/scala/io/cardell/ff4s/flipt/model/EvaluationRequest.scala index 022dc4c..6c657d1 100644 --- a/flipt/sdk-server/shared/src/main/scala/io/cardell/ff4s/flipt/model/EvaluationRequest.scala +++ b/flipt/sdk-server/shared/src/main/scala/io/cardell/ff4s/flipt/model/EvaluationRequest.scala @@ -16,8 +16,8 @@ package io.cardell.ff4s.flipt -import io.circe.generic.semiauto.deriveEncoder import io.circe.Encoder +import io.circe.generic.semiauto.deriveEncoder case class EvaluationRequest( namespaceKey: String, diff --git a/flipt/sdk-server/shared/src/main/scala/io/cardell/ff4s/flipt/model/EvaluationResponse.scala b/flipt/sdk-server/shared/src/main/scala/io/cardell/ff4s/flipt/model/EvaluationResponse.scala index 99d9cab..8a8df98 100644 --- a/flipt/sdk-server/shared/src/main/scala/io/cardell/ff4s/flipt/model/EvaluationResponse.scala +++ b/flipt/sdk-server/shared/src/main/scala/io/cardell/ff4s/flipt/model/EvaluationResponse.scala @@ -23,12 +23,14 @@ import io.circe.generic.semiauto.deriveDecoder sealed trait EvaluationResponse object EvaluationResponse { + implicit def decoder: Decoder[EvaluationResponse] = List[Decoder[EvaluationResponse]]( Decoder[BooleanEvaluationResponse].widen, Decoder[VariantEvaluationResponse].widen, Decoder[ErrorEvaluationResponse].widen ).reduceLeft(_ or _) + } case class BooleanEvaluationResponse( diff --git a/flipt/sdk-server/shared/src/main/scala/io/cardell/ff4s/flipt/model/StructuredEvaluationResponse.scala b/flipt/sdk-server/shared/src/main/scala/io/cardell/ff4s/flipt/model/StructuredEvaluationResponse.scala index 45feea0..a8c483c 100644 --- a/flipt/sdk-server/shared/src/main/scala/io/cardell/ff4s/flipt/model/StructuredEvaluationResponse.scala +++ b/flipt/sdk-server/shared/src/main/scala/io/cardell/ff4s/flipt/model/StructuredEvaluationResponse.scala @@ -16,13 +16,14 @@ package io.cardell.ff4s.flipt.model -import cats.syntax.all.* +import cats.syntax.all._ import io.circe.Decoder import io.circe.parser sealed trait AttachmentDecodingError + object AttachmentDecodingError { - case object AttachmentJsonParsingError extends AttachmentDecodingError + case object AttachmentJsonParsingError extends AttachmentDecodingError case object AttachmentDeserialisationError extends AttachmentDecodingError } @@ -38,14 +39,16 @@ case class StructuredVariantEvaluationResponse[A]( ) object StructuredVariantEvaluationResponse { + def apply[A: Decoder]( variant: VariantEvaluationResponse ): Either[AttachmentDecodingError, StructuredVariantEvaluationResponse[A]] = { - val maybeAttachment = if (variant.`match`) { - decodeJsonAttachment(variant.variantAttachment).map(Some(_)) - } else { - Option.empty[A].asRight[AttachmentDecodingError] - } + val maybeAttachment = + if (variant.`match`) { + decodeJsonAttachment(variant.variantAttachment).map(Some(_)) + } else { + Option.empty[A].asRight[AttachmentDecodingError] + } maybeAttachment.map { attachment => StructuredVariantEvaluationResponse[A]( diff --git a/open-feature/provider-flipt-it/src/test/scala/io/cardell/openfeature/provider/flipt/FliptProviderItTest.scala b/open-feature/provider-flipt-it/src/test/scala/io/cardell/openfeature/provider/flipt/FliptProviderItTest.scala new file mode 100644 index 0000000..392c0f8 --- /dev/null +++ b/open-feature/provider-flipt-it/src/test/scala/io/cardell/openfeature/provider/flipt/FliptProviderItTest.scala @@ -0,0 +1,186 @@ +/* + * Copyright 2023 Alex Cardell + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.cardell.openfeature.provider.flipt + +import cats.effect.IO +import cats.effect.kernel.Resource +import com.dimafeng.testcontainers.ContainerDef +import com.dimafeng.testcontainers.DockerComposeContainer +import com.dimafeng.testcontainers.ExposedService +import com.dimafeng.testcontainers.munit.TestContainerForAll +import java.io.File +import munit.CatsEffectSuite +import org.http4s.Uri +import org.http4s.ember.client.EmberClientBuilder +import org.testcontainers.containers.wait.strategy.Wait + +import io.cardell.ff4s.flipt.FliptApi +import io.cardell.ff4s.flipt.auth.AuthenticationStrategy +import io.cardell.openfeature.EvaluationContext + +class FliptProviderItTest extends CatsEffectSuite with TestContainerForAll { + + override val containerDef: ContainerDef = DockerComposeContainer.Def( + new File("docker-compose.yaml"), + exposedServices = Seq( + ExposedService("flipt", 8080, Wait.forLogMessage("^UI: http.*", 1)) + ), + tailChildContainers = true + ) + + def api(containers: Containers): Resource[IO, FliptProvider[IO]] = { + val flipt = + containers + .asInstanceOf[DockerComposeContainer] + .getContainerByServiceName("flipt") + .get + + val url = Uri + .fromString( + s"http://${flipt.getHost()}:${flipt.getMappedPort(8080)}" + ) + .getOrElse(throw new Exception("invalid url")) + + EmberClientBuilder + .default[IO] + .build + .map { client => + val flipt = FliptApi[IO]( + client, + url, + AuthenticationStrategy.ClientToken("token") + ) + + new FliptProvider(flipt, "default") + } + } + + test("can fetch boolean flag") { + withContainers { containers => + api(containers).use { flipt => + for { + res <- flipt.resolveBooleanValue( + "boolean-flag-1", + false, + EvaluationContext.empty + ) + } yield assertEquals(res.value, true) + } + } + } + + // test("uses default when boolean flag missing") { + // withContainers { containers => + // api(containers).use { flipt => + // for { + // res <- flipt.resolveBooleanValue( + // "no-flag", + // false, + // EvaluationContext.empty + // ) + // } yield assertEquals(res.value, false) + // } + // } + // } + + // test("receives variant match when in segment rule") { + // withContainers { containers => + // api(containers).use { flipt => + // val segmentContext = Map("test-property" -> "matched-property-value") + // for { + // res <- flipt.evaluateVariant( + // EvaluationRequest( + // "default", + // "variant-flag-1", + // None, + // segmentContext, + // None + // ) + // ) + // } yield assertEquals(res.`match`, true) + // } + // } + // } + // + // test("receives no variant match when not in segment rule") { + // withContainers { containers => + // api(containers).use { flipt => + // val segmentContext = Map("test-property" -> "unmatched-property-value") + // for { + // res <- flipt.evaluateVariant( + // EvaluationRequest( + // "default", + // "variant-flag-1", + // None, + // segmentContext, + // None + // ) + // ) + // } yield assertEquals(res.`match`, false) + // } + // } + // } + // + // case class TestVariant(field: String, intField: Int) + // object TestVariant { + // implicit val decoder: Decoder[TestVariant] = deriveDecoder + // } + // + // test("can deserialise variant match") { + // withContainers { containers => + // api(containers).use { flipt => + // val segmentContext = Map("test-property" -> "matched-property-value") + // + // for { + // res <- flipt.evaluateStructuredVariant[TestVariant]( + // EvaluationRequest( + // "default", + // "variant-flag-1", + // None, + // segmentContext, + // None + // ) + // ) + // _ <- IO.println(res) + // result = res.map(_.variantAttachment) + // } yield assertEquals(result, Right(Some(TestVariant("string", 33)))) + // } + // } + // } + // + // test("does not attempt variant deserialisation without a match") { + // withContainers { containers => + // api(containers).use { flipt => + // val segmentContext = Map("test-property" -> "unmatched-property-value") + // + // for { + // res <- flipt.evaluateStructuredVariant[TestVariant]( + // EvaluationRequest( + // "default", + // "variant-flag-1", + // None, + // segmentContext, + // None + // ) + // ) + // _ <- IO.println(res) + // result = res.map(_.variantAttachment) + // } yield assertEquals(result, Right(None)) + // } + // } + // } +} diff --git a/open-feature/provider-flipt/src/main/scala/io/cardell/openfeature/provider/flipt/FliptProvider.scala b/open-feature/provider-flipt/src/main/scala/io/cardell/openfeature/provider/flipt/FliptProvider.scala index d3dbdbf..4b53268 100644 --- a/open-feature/provider-flipt/src/main/scala/io/cardell/openfeature/provider/flipt/FliptProvider.scala +++ b/open-feature/provider-flipt/src/main/scala/io/cardell/openfeature/provider/flipt/FliptProvider.scala @@ -1,21 +1,38 @@ +/* + * Copyright 2023 Alex Cardell + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package io.cardell.openfeature.provider.flipt +import cats.MonadThrow +import cats.syntax.all._ + +import io.cardell.ff4s.flipt.EvaluationRequest +import io.cardell.ff4s.flipt.FliptApi +import io.cardell.ff4s.flipt.model.{EvaluationReason => FliptReason} +import io.cardell.openfeature.ErrorCode import io.cardell.openfeature.EvaluationContext -import io.cardell.openfeature.StructureDecoder +import io.cardell.openfeature.EvaluationReason import io.cardell.openfeature.provider.Provider import io.cardell.openfeature.provider.ProviderMetadata import io.cardell.openfeature.provider.ResolutionDetails -import io.cardell.ff4s.flipt.FliptApi -import io.cardell.ff4s.flipt.EvaluationRequest -import io.cardell.ff4s.flipt.model.{EvaluationReason => FliptReason} -import io.cardell.openfeature.EvaluationReason -import cats.Monad -import cats.syntax.all.* -class FliptProvider[F[_]: Monad](flipt: FliptApi[F], namespace: String) +class FliptProvider[F[_]: MonadThrow](flipt: FliptApi[F], namespace: String) extends Provider[F] { - override def metadata: ProviderMetadata = ??? + override def metadata: ProviderMetadata = ProviderMetadata(name = "flipt") private def mapReason(evalReason: FliptReason): EvaluationReason = evalReason match { @@ -25,12 +42,15 @@ class FliptProvider[F[_]: Monad](flipt: FliptApi[F], namespace: String) case FliptReason.Unknown => EvaluationReason.Unknown } + private def mapContext(context: EvaluationContext): Map[String, String] = + context.values.map { case (k, v) => (k, v.stringValue) } + override def resolveBooleanValue( flagKey: String, defaultValue: Boolean, context: EvaluationContext ): F[ResolutionDetails[Boolean]] = { - val evalContext = context.values.map { case (k, v) => (k, v.stringValue) } + val evalContext = mapContext(context) val req: EvaluationRequest = EvaluationRequest( namespaceKey = namespace, @@ -40,7 +60,7 @@ class FliptProvider[F[_]: Monad](flipt: FliptApi[F], namespace: String) reference = None ) - flipt.evaluateBoolean(req).map { evaluation => + val resolution = flipt.evaluateBoolean(req).map { evaluation => ResolutionDetails[Boolean]( value = evaluation.enabled, errorCode = None, @@ -50,30 +70,44 @@ class FliptProvider[F[_]: Monad](flipt: FliptApi[F], namespace: String) metadata = None ) } - } - override def resolveStringValue( - flagKey: String, - defaultValue: String, - context: EvaluationContext - ): F[ResolutionDetails[String]] = ??? - - override def resolveIntValue( - flagKey: String, - defaultValue: Int, - context: EvaluationContext - ): F[ResolutionDetails[Int]] = ??? + def default(t: Throwable) = ResolutionDetails[Boolean]( + value = defaultValue, + errorCode = Some(ErrorCode.General), + errorMessage = Some(t.getMessage()), + reason = Some(EvaluationReason.Default), + variant = None, + metadata = None + ) - override def resolveDoubleValue( - flagKey: String, - defaultValue: Double, - context: EvaluationContext - ): F[ResolutionDetails[Double]] = ??? + resolution.attempt.map { + case Right(value) => value + case Left(error) => default(error) + } + } - override def resolveStructureValue[A: StructureDecoder]( - flagKey: String, - defaultValue: A, - context: EvaluationContext - ): F[ResolutionDetails[A]] = ??? + // override def resolveStringValue( + // flagKey: String, + // defaultValue: String, + // context: EvaluationContext + // ): F[ResolutionDetails[String]] = ??? + // + // override def resolveIntValue( + // flagKey: String, + // defaultValue: Int, + // context: EvaluationContext + // ): F[ResolutionDetails[Int]] = ??? + // + // override def resolveDoubleValue( + // flagKey: String, + // defaultValue: Double, + // context: EvaluationContext + // ): F[ResolutionDetails[Double]] = ??? + // + // override def resolveStructureValue[A: StructureDecoder]( + // flagKey: String, + // defaultValue: A, + // context: EvaluationContext + // ): F[ResolutionDetails[A]] = ??? } diff --git a/open-feature/sdk/src/main/scala/io/cardell/openfeature/ErrorCode.scala b/open-feature/sdk/src/main/scala/io/cardell/openfeature/ErrorCode.scala index c72423f..f9ac750 100644 --- a/open-feature/sdk/src/main/scala/io/cardell/openfeature/ErrorCode.scala +++ b/open-feature/sdk/src/main/scala/io/cardell/openfeature/ErrorCode.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2023 Alex Cardell + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package io.cardell.openfeature sealed trait ErrorCode @@ -30,4 +46,3 @@ object ErrorCode { /** The error was for a reason not enumerated above. */ case object General extends ErrorCode } - diff --git a/open-feature/sdk/src/main/scala/io/cardell/openfeature/EvaluationContext.scala b/open-feature/sdk/src/main/scala/io/cardell/openfeature/EvaluationContext.scala index 4f09264..f865e11 100644 --- a/open-feature/sdk/src/main/scala/io/cardell/openfeature/EvaluationContext.scala +++ b/open-feature/sdk/src/main/scala/io/cardell/openfeature/EvaluationContext.scala @@ -1,35 +1,56 @@ +/* + * Copyright 2023 Alex Cardell + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package io.cardell.openfeature + import io.cardell.openfeature.ContextValue.BooleanValue -import io.cardell.openfeature.ContextValue.StringValue -import io.cardell.openfeature.ContextValue.IntValue import io.cardell.openfeature.ContextValue.DoubleValue +import io.cardell.openfeature.ContextValue.IntValue +import io.cardell.openfeature.ContextValue.StringValue sealed trait ContextValue { - def stringValue: String = this match { - case StringValue(value) => value - case BooleanValue(value) => value.toString() - case IntValue(value) => value.toString() - case DoubleValue(value) => value.toString() - } + + def stringValue: String = + this match { + case StringValue(value) => value + case BooleanValue(value) => value.toString() + case IntValue(value) => value.toString() + case DoubleValue(value) => value.toString() + } + } object ContextValue { case class BooleanValue(value: Boolean) extends ContextValue - case class StringValue(value: String) extends ContextValue - case class IntValue(value: Int) extends ContextValue - case class DoubleValue(value: Double) extends ContextValue + case class StringValue(value: String) extends ContextValue + case class IntValue(value: Int) extends ContextValue + case class DoubleValue(value: Double) extends ContextValue } case class EvaluationContext( targetingKey: Option[String], values: Map[String, ContextValue] ) { - def ++(other: EvaluationContext): EvaluationContext = - EvaluationContext( - other.targetingKey.orElse(targetingKey), - // TODO check override order in spec - values ++ other.values - ) + + def ++(other: EvaluationContext): EvaluationContext = EvaluationContext( + other.targetingKey.orElse(targetingKey), + // TODO check override order in spec + values ++ other.values + ) + } object EvaluationContext { diff --git a/open-feature/sdk/src/main/scala/io/cardell/openfeature/EvaluationDetails.scala b/open-feature/sdk/src/main/scala/io/cardell/openfeature/EvaluationDetails.scala index 9219894..bdf9431 100644 --- a/open-feature/sdk/src/main/scala/io/cardell/openfeature/EvaluationDetails.scala +++ b/open-feature/sdk/src/main/scala/io/cardell/openfeature/EvaluationDetails.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2023 Alex Cardell + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package io.cardell.openfeature import io.cardell.openfeature.provider.FlagMetadata @@ -18,14 +34,14 @@ object EvaluationDetails { def apply[A]( flagKey: String, resolution: ResolutionDetails[A] - ): EvaluationDetails[A] = - EvaluationDetails[A]( - flagKey = flagKey, - value = resolution.value, - errorCode = resolution.errorCode, - errorMessage = resolution.errorMessage, - reason = resolution.reason, - variant = resolution.variant, - metadata = resolution.metadata - ) + ): EvaluationDetails[A] = EvaluationDetails[A]( + flagKey = flagKey, + value = resolution.value, + errorCode = resolution.errorCode, + errorMessage = resolution.errorMessage, + reason = resolution.reason, + variant = resolution.variant, + metadata = resolution.metadata + ) + } diff --git a/open-feature/sdk/src/main/scala/io/cardell/openfeature/EvaluationReason.scala b/open-feature/sdk/src/main/scala/io/cardell/openfeature/EvaluationReason.scala index 9ef986a..1824815 100644 --- a/open-feature/sdk/src/main/scala/io/cardell/openfeature/EvaluationReason.scala +++ b/open-feature/sdk/src/main/scala/io/cardell/openfeature/EvaluationReason.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2023 Alex Cardell + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package io.cardell.openfeature sealed trait EvaluationReason diff --git a/open-feature/sdk/src/main/scala/io/cardell/openfeature/FeatureClient.scala b/open-feature/sdk/src/main/scala/io/cardell/openfeature/FeatureClient.scala index f170315..087c516 100644 --- a/open-feature/sdk/src/main/scala/io/cardell/openfeature/FeatureClient.scala +++ b/open-feature/sdk/src/main/scala/io/cardell/openfeature/FeatureClient.scala @@ -1,11 +1,28 @@ +/* + * Copyright 2023 Alex Cardell + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package io.cardell.openfeature import io.cardell.openfeature.provider.ProviderMetadata // TODO implement circe -trait StructureDecoder[A] +// trait StructureDecoder[A] case class EvaluationOptions() + object EvaluationOptions { val Defaults: EvaluationOptions = EvaluationOptions() } @@ -22,11 +39,13 @@ trait FeatureClient[F[_]] { // def withHooks(hooks: List[Hook]): FeatureClient[F] def getBooleanValue(flagKey: String, default: Boolean): F[Boolean] + def getBooleanValue( flagKey: String, default: Boolean, context: EvaluationContext ): F[Boolean] + def getBooleanValue( flagKey: String, default: Boolean, @@ -38,11 +57,13 @@ trait FeatureClient[F[_]] { flagKey: String, default: Boolean ): F[EvaluationDetails[Boolean]] + def getBooleanDetails( flagKey: String, default: Boolean, context: EvaluationContext ): F[EvaluationDetails[Boolean]] + def getBooleanDetails( flagKey: String, default: Boolean, @@ -50,119 +71,119 @@ trait FeatureClient[F[_]] { options: EvaluationOptions ): F[EvaluationDetails[Boolean]] - def getStringValue(flagKey: String, default: String): F[String] - def getStringValue( - flagKey: String, - default: String, - context: EvaluationContext - ): F[String] - def getStringValue( - flagKey: String, - default: String, - context: EvaluationContext, - options: EvaluationOptions - ): F[String] - - def getStringDetails( - flagKey: String, - default: String - ): F[EvaluationDetails[String]] - def getStringDetails( - flagKey: String, - default: String, - context: EvaluationContext - ): F[EvaluationDetails[String]] - def getStringDetails( - flagKey: String, - default: String, - context: EvaluationContext, - options: EvaluationOptions - ): F[EvaluationDetails[String]] - - def getIntValue(flagKey: String, default: Int): F[Int] - def getIntValue( - flagKey: String, - default: Int, - context: EvaluationContext - ): F[Int] - def getIntValue( - flagKey: String, - default: Int, - context: EvaluationContext, - options: EvaluationOptions - ): F[Int] - - def getIntDetails(flagKey: String, default: Int): F[EvaluationDetails[Int]] - def getIntDetails( - flagKey: String, - default: Int, - context: EvaluationContext - ): F[EvaluationDetails[Int]] - def getIntDetails( - flagKey: String, - default: Int, - context: EvaluationContext, - options: EvaluationOptions - ): F[EvaluationDetails[Int]] - - def getDoubleValue(flagKey: String, default: Double): F[Double] - def getDoubleValue( - flagKey: String, - default: Double, - context: EvaluationContext - ): F[Double] - def getDoubleValue( - flagKey: String, - default: Double, - context: EvaluationContext, - options: EvaluationOptions - ): F[Double] - - def getDoubleDetails( - flagKey: String, - default: Double - ): F[EvaluationDetails[Double]] - def getDoubleDetails( - flagKey: String, - default: Double, - context: EvaluationContext - ): F[EvaluationDetails[Double]] - def getDoubleDetails( - flagKey: String, - default: Double, - context: EvaluationContext, - options: EvaluationOptions - ): F[EvaluationDetails[Double]] - - def getStructureValue[A: StructureDecoder]( - flagKey: String, - default: A - ): F[A] - def getStructureValue[A: StructureDecoder]( - flagKey: String, - default: A, - context: EvaluationContext - ): F[A] - def getStructureValue[A: StructureDecoder]( - flagKey: String, - default: A, - context: EvaluationContext, - options: EvaluationOptions - ): F[A] - - def getStructureDetails[A: StructureDecoder]( - flagKey: String, - default: A - ): F[EvaluationDetails[A]] - def getStructureDetails[A: StructureDecoder]( - flagKey: String, - default: A, - context: EvaluationContext - ): F[EvaluationDetails[A]] - def getStructureDetails[A: StructureDecoder]( - flagKey: String, - default: A, - context: EvaluationContext, - options: EvaluationOptions - ): F[EvaluationDetails[A]] + // def getStringValue(flagKey: String, default: String): F[String] + // def getStringValue( + // flagKey: String, + // default: String, + // context: EvaluationContext + // ): F[String] + // def getStringValue( + // flagKey: String, + // default: String, + // context: EvaluationContext, + // options: EvaluationOptions + // ): F[String] + // + // def getStringDetails( + // flagKey: String, + // default: String + // ): F[EvaluationDetails[String]] + // def getStringDetails( + // flagKey: String, + // default: String, + // context: EvaluationContext + // ): F[EvaluationDetails[String]] + // def getStringDetails( + // flagKey: String, + // default: String, + // context: EvaluationContext, + // options: EvaluationOptions + // ): F[EvaluationDetails[String]] + // + // def getIntValue(flagKey: String, default: Int): F[Int] + // def getIntValue( + // flagKey: String, + // default: Int, + // context: EvaluationContext + // ): F[Int] + // def getIntValue( + // flagKey: String, + // default: Int, + // context: EvaluationContext, + // options: EvaluationOptions + // ): F[Int] + // + // def getIntDetails(flagKey: String, default: Int): F[EvaluationDetails[Int]] + // def getIntDetails( + // flagKey: String, + // default: Int, + // context: EvaluationContext + // ): F[EvaluationDetails[Int]] + // def getIntDetails( + // flagKey: String, + // default: Int, + // context: EvaluationContext, + // options: EvaluationOptions + // ): F[EvaluationDetails[Int]] + // + // def getDoubleValue(flagKey: String, default: Double): F[Double] + // def getDoubleValue( + // flagKey: String, + // default: Double, + // context: EvaluationContext + // ): F[Double] + // def getDoubleValue( + // flagKey: String, + // default: Double, + // context: EvaluationContext, + // options: EvaluationOptions + // ): F[Double] + // + // def getDoubleDetails( + // flagKey: String, + // default: Double + // ): F[EvaluationDetails[Double]] + // def getDoubleDetails( + // flagKey: String, + // default: Double, + // context: EvaluationContext + // ): F[EvaluationDetails[Double]] + // def getDoubleDetails( + // flagKey: String, + // default: Double, + // context: EvaluationContext, + // options: EvaluationOptions + // ): F[EvaluationDetails[Double]] + // + // def getStructureValue[A: StructureDecoder]( + // flagKey: String, + // default: A + // ): F[A] + // def getStructureValue[A: StructureDecoder]( + // flagKey: String, + // default: A, + // context: EvaluationContext + // ): F[A] + // def getStructureValue[A: StructureDecoder]( + // flagKey: String, + // default: A, + // context: EvaluationContext, + // options: EvaluationOptions + // ): F[A] + // + // def getStructureDetails[A: StructureDecoder]( + // flagKey: String, + // default: A + // ): F[EvaluationDetails[A]] + // def getStructureDetails[A: StructureDecoder]( + // flagKey: String, + // default: A, + // context: EvaluationContext + // ): F[EvaluationDetails[A]] + // def getStructureDetails[A: StructureDecoder]( + // flagKey: String, + // default: A, + // context: EvaluationContext, + // options: EvaluationOptions + // ): F[EvaluationDetails[A]] } diff --git a/open-feature/sdk/src/main/scala/io/cardell/openfeature/FeatureClientImpl.scala b/open-feature/sdk/src/main/scala/io/cardell/openfeature/FeatureClientImpl.scala index 6f7d81d..ea68066 100644 --- a/open-feature/sdk/src/main/scala/io/cardell/openfeature/FeatureClientImpl.scala +++ b/open-feature/sdk/src/main/scala/io/cardell/openfeature/FeatureClientImpl.scala @@ -1,7 +1,24 @@ +/* + * Copyright 2023 Alex Cardell + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package io.cardell.openfeature import cats.Monad import cats.syntax.all._ + import io.cardell.openfeature.provider.Provider import io.cardell.openfeature.provider.ProviderMetadata @@ -30,232 +47,236 @@ protected[openfeature] final class FeatureClientImpl[F[_]: Monad]( flagKey: String, default: Boolean, context: EvaluationContext - ): F[Boolean] = - getBooleanValue( - flagKey, - default, - context, - EvaluationOptions.Defaults - ) + ): F[Boolean] = getBooleanValue( + flagKey, + default, + context, + EvaluationOptions.Defaults + ) override def getBooleanValue( flagKey: String, default: Boolean, context: EvaluationContext, options: EvaluationOptions // TODO handle options - ): F[Boolean] = - getBooleanDetails(flagKey, default, context, options) - .map(_.value) + ): F[Boolean] = getBooleanDetails(flagKey, default, context, options) + .map(_.value) override def getBooleanDetails( flagKey: String, default: Boolean - ): F[EvaluationDetails[Boolean]] = - getBooleanDetails(flagKey, default, EvaluationContext.empty) + ): F[EvaluationDetails[Boolean]] = getBooleanDetails( + flagKey, + default, + EvaluationContext.empty + ) override def getBooleanDetails( flagKey: String, default: Boolean, context: EvaluationContext - ): F[EvaluationDetails[Boolean]] = - getBooleanDetails(flagKey, default, context, EvaluationOptions.Defaults) + ): F[EvaluationDetails[Boolean]] = getBooleanDetails( + flagKey, + default, + context, + EvaluationOptions.Defaults + ) override def getBooleanDetails( flagKey: String, default: Boolean, context: EvaluationContext, options: EvaluationOptions - ): F[EvaluationDetails[Boolean]] = - provider - .resolveBooleanValue( - flagKey, - default, - clientEvaluationContext ++ context - ) - .map(EvaluationDetails[Boolean](flagKey, _)) - - override def getStringValue(flagKey: String, default: String): F[String] = - getStringValue(flagKey, default, EvaluationContext.empty) - - override def getStringValue( - flagKey: String, - default: String, - context: EvaluationContext - ): F[String] = - getStringValue( - flagKey, - default, - context, - EvaluationOptions.Defaults - ) - - override def getStringValue( - flagKey: String, - default: String, - context: EvaluationContext, - options: EvaluationOptions - ): F[String] = - getStringDetails(flagKey, default, context, EvaluationOptions.Defaults) - .map(_.value) - - override def getStringDetails( - flagKey: String, - default: String - ): F[EvaluationDetails[String]] = - getStringDetails(flagKey, default, EvaluationContext.empty) - - override def getStringDetails( - flagKey: String, - default: String, - context: EvaluationContext - ): F[EvaluationDetails[String]] = - getStringDetails(flagKey, default, context, EvaluationOptions.Defaults) - - override def getStringDetails( - flagKey: String, - default: String, - context: EvaluationContext, - options: EvaluationOptions - ): F[EvaluationDetails[String]] = - provider - .resolveStringValue(flagKey, default, clientEvaluationContext ++ context) - .map(EvaluationDetails[String](flagKey, _)) - - override def getIntValue(flagKey: String, default: Int): F[Int] = - getIntValue(flagKey, default, EvaluationContext.empty) - - override def getIntValue( - flagKey: String, - default: Int, - context: EvaluationContext - ): F[Int] = getIntValue(flagKey, default, context, EvaluationOptions.Defaults) - - override def getIntValue( - flagKey: String, - default: Int, - context: EvaluationContext, - options: EvaluationOptions - ): F[Int] = - getIntDetails(flagKey, default, context, options) - .map(_.value) - - override def getIntDetails( - flagKey: String, - default: Int - ): F[EvaluationDetails[Int]] = - getIntDetails(flagKey, default, EvaluationContext.empty) - - override def getIntDetails( - flagKey: String, - default: Int, - context: EvaluationContext - ): F[EvaluationDetails[Int]] = - getIntDetails(flagKey, default, context, EvaluationOptions.Defaults) - - override def getIntDetails( - flagKey: String, - default: Int, - context: EvaluationContext, - options: EvaluationOptions - ): F[EvaluationDetails[Int]] = - provider - .resolveIntValue( - flagKey, - default, - clientEvaluationContext ++ context - ) - .map(EvaluationDetails[Int](flagKey, _)) - - override def getDoubleValue(flagKey: String, default: Double): F[Double] = - getDoubleValue(flagKey, default, EvaluationContext.empty) - - override def getDoubleValue( - flagKey: String, - default: Double, - context: EvaluationContext - ): F[Double] = - getDoubleValue(flagKey, default, context, EvaluationOptions.Defaults) - - override def getDoubleValue( - flagKey: String, - default: Double, - context: EvaluationContext, - options: EvaluationOptions - ): F[Double] = - provider - .resolveDoubleValue( - flagKey, - default, - clientEvaluationContext ++ context - ) - .map(_.value) - - override def getDoubleDetails( - flagKey: String, - default: Double - ): F[EvaluationDetails[Double]] = ??? - override def getDoubleDetails( - flagKey: String, - default: Double, - context: EvaluationContext - ): F[EvaluationDetails[Double]] = ??? - override def getDoubleDetails( - flagKey: String, - default: Double, - context: EvaluationContext, - options: EvaluationOptions - ): F[EvaluationDetails[Double]] = ??? - - override def getStructureValue[A: StructureDecoder]( - flagKey: String, - default: A - ): F[A] = - getStructureValue[A](flagKey, default, EvaluationContext.empty) - - override def getStructureValue[A: StructureDecoder]( - flagKey: String, - default: A, - context: EvaluationContext - ): F[A] = - getStructureValue[A](flagKey, default, context, EvaluationOptions.Defaults) - - override def getStructureValue[A: StructureDecoder]( - flagKey: String, - default: A, - context: EvaluationContext, - options: EvaluationOptions - ): F[A] = - getStructureDetails[A](flagKey, default, context) - .map(_.value) - - override def getStructureDetails[A: StructureDecoder]( - flagKey: String, - default: A - ): F[EvaluationDetails[A]] = - getStructureDetails[A](flagKey, default, EvaluationContext.empty) - override def getStructureDetails[A: StructureDecoder]( - flagKey: String, - default: A, - context: EvaluationContext - ): F[EvaluationDetails[A]] = - getStructureDetails[A]( + ): F[EvaluationDetails[Boolean]] = provider + .resolveBooleanValue( flagKey, default, - context, - EvaluationOptions.Defaults + clientEvaluationContext ++ context ) - - override def getStructureDetails[A: StructureDecoder]( - flagKey: String, - default: A, - context: EvaluationContext, - options: EvaluationOptions - ): F[EvaluationDetails[A]] = - provider - .resolveStructureValue[A]( - flagKey, - default, - clientEvaluationContext ++ context - ) - .map(EvaluationDetails(flagKey, _)) + .map(EvaluationDetails[Boolean](flagKey, _)) + + // override def getStringValue(flagKey: String, default: String): F[String] = + // getStringValue(flagKey, default, EvaluationContext.empty) + // + // override def getStringValue( + // flagKey: String, + // default: String, + // context: EvaluationContext + // ): F[String] = + // getStringValue( + // flagKey, + // default, + // context, + // EvaluationOptions.Defaults + // ) + // + // override def getStringValue( + // flagKey: String, + // default: String, + // context: EvaluationContext, + // options: EvaluationOptions + // ): F[String] = + // getStringDetails(flagKey, default, context, EvaluationOptions.Defaults) + // .map(_.value) + // + // override def getStringDetails( + // flagKey: String, + // default: String + // ): F[EvaluationDetails[String]] = + // getStringDetails(flagKey, default, EvaluationContext.empty) + // + // override def getStringDetails( + // flagKey: String, + // default: String, + // context: EvaluationContext + // ): F[EvaluationDetails[String]] = + // getStringDetails(flagKey, default, context, EvaluationOptions.Defaults) + // + // override def getStringDetails( + // flagKey: String, + // default: String, + // context: EvaluationContext, + // options: EvaluationOptions + // ): F[EvaluationDetails[String]] = + // provider + // .resolveStringValue(flagKey, default, clientEvaluationContext ++ context) + // .map(EvaluationDetails[String](flagKey, _)) + // + // override def getIntValue(flagKey: String, default: Int): F[Int] = + // getIntValue(flagKey, default, EvaluationContext.empty) + // + // override def getIntValue( + // flagKey: String, + // default: Int, + // context: EvaluationContext + // ): F[Int] = getIntValue(flagKey, default, context, EvaluationOptions.Defaults) + // + // override def getIntValue( + // flagKey: String, + // default: Int, + // context: EvaluationContext, + // options: EvaluationOptions + // ): F[Int] = + // getIntDetails(flagKey, default, context, options) + // .map(_.value) + // + // override def getIntDetails( + // flagKey: String, + // default: Int + // ): F[EvaluationDetails[Int]] = + // getIntDetails(flagKey, default, EvaluationContext.empty) + // + // override def getIntDetails( + // flagKey: String, + // default: Int, + // context: EvaluationContext + // ): F[EvaluationDetails[Int]] = + // getIntDetails(flagKey, default, context, EvaluationOptions.Defaults) + // + // override def getIntDetails( + // flagKey: String, + // default: Int, + // context: EvaluationContext, + // options: EvaluationOptions + // ): F[EvaluationDetails[Int]] = + // provider + // .resolveIntValue( + // flagKey, + // default, + // clientEvaluationContext ++ context + // ) + // .map(EvaluationDetails[Int](flagKey, _)) + // + // override def getDoubleValue(flagKey: String, default: Double): F[Double] = + // getDoubleValue(flagKey, default, EvaluationContext.empty) + // + // override def getDoubleValue( + // flagKey: String, + // default: Double, + // context: EvaluationContext + // ): F[Double] = + // getDoubleValue(flagKey, default, context, EvaluationOptions.Defaults) + // + // override def getDoubleValue( + // flagKey: String, + // default: Double, + // context: EvaluationContext, + // options: EvaluationOptions + // ): F[Double] = + // provider + // .resolveDoubleValue( + // flagKey, + // default, + // clientEvaluationContext ++ context + // ) + // .map(_.value) + // + // override def getDoubleDetails( + // flagKey: String, + // default: Double + // ): F[EvaluationDetails[Double]] = ??? + // override def getDoubleDetails( + // flagKey: String, + // default: Double, + // context: EvaluationContext + // ): F[EvaluationDetails[Double]] = ??? + // override def getDoubleDetails( + // flagKey: String, + // default: Double, + // context: EvaluationContext, + // options: EvaluationOptions + // ): F[EvaluationDetails[Double]] = ??? + // + // override def getStructureValue[A: StructureDecoder]( + // flagKey: String, + // default: A + // ): F[A] = + // getStructureValue[A](flagKey, default, EvaluationContext.empty) + // + // override def getStructureValue[A: StructureDecoder]( + // flagKey: String, + // default: A, + // context: EvaluationContext + // ): F[A] = + // getStructureValue[A](flagKey, default, context, EvaluationOptions.Defaults) + // + // override def getStructureValue[A: StructureDecoder]( + // flagKey: String, + // default: A, + // context: EvaluationContext, + // options: EvaluationOptions + // ): F[A] = + // getStructureDetails[A](flagKey, default, context) + // .map(_.value) + // + // override def getStructureDetails[A: StructureDecoder]( + // flagKey: String, + // default: A + // ): F[EvaluationDetails[A]] = + // getStructureDetails[A](flagKey, default, EvaluationContext.empty) + // override def getStructureDetails[A: StructureDecoder]( + // flagKey: String, + // default: A, + // context: EvaluationContext + // ): F[EvaluationDetails[A]] = + // getStructureDetails[A]( + // flagKey, + // default, + // context, + // EvaluationOptions.Defaults + // ) + // + // override def getStructureDetails[A: StructureDecoder]( + // flagKey: String, + // default: A, + // context: EvaluationContext, + // options: EvaluationOptions + // ): F[EvaluationDetails[A]] = + // provider + // .resolveStructureValue[A]( + // flagKey, + // default, + // clientEvaluationContext ++ context + // ) + // .map(EvaluationDetails(flagKey, _)) } diff --git a/open-feature/sdk/src/main/scala/io/cardell/openfeature/OpenFeature.scala b/open-feature/sdk/src/main/scala/io/cardell/openfeature/OpenFeature.scala index c63cabc..9512fa7 100644 --- a/open-feature/sdk/src/main/scala/io/cardell/openfeature/OpenFeature.scala +++ b/open-feature/sdk/src/main/scala/io/cardell/openfeature/OpenFeature.scala @@ -1,7 +1,24 @@ +/* + * Copyright 2023 Alex Cardell + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package io.cardell.openfeature import cats.Monad -import cats.syntax.all.* +import cats.syntax.all._ + import io.cardell.openfeature.EvaluationContext import io.cardell.openfeature.provider.Provider @@ -14,6 +31,8 @@ trait OpenFeature[F[_]] { protected[openfeature] class OpenFeatureSdk[F[_]: Monad](provider: Provider[F]) extends OpenFeature[F] { + def client: F[FeatureClient[F]] = new FeatureClientImpl[F](provider, EvaluationContext.empty).pure[F].widen + } diff --git a/open-feature/sdk/src/main/scala/io/cardell/openfeature/package.scala b/open-feature/sdk/src/main/scala/io/cardell/openfeature/package.scala index d439a39..58bb1a6 100644 --- a/open-feature/sdk/src/main/scala/io/cardell/openfeature/package.scala +++ b/open-feature/sdk/src/main/scala/io/cardell/openfeature/package.scala @@ -1,5 +1,19 @@ -package io.cardell +/* + * Copyright 2023 Alex Cardell + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ -package object openfeature { +package io.cardell -} +package object openfeature {} diff --git a/open-feature/sdk/src/main/scala/io/cardell/openfeature/provider/Provider.scala b/open-feature/sdk/src/main/scala/io/cardell/openfeature/provider/Provider.scala index 00d6f35..2fa35f8 100644 --- a/open-feature/sdk/src/main/scala/io/cardell/openfeature/provider/Provider.scala +++ b/open-feature/sdk/src/main/scala/io/cardell/openfeature/provider/Provider.scala @@ -1,7 +1,23 @@ +/* + * Copyright 2023 Alex Cardell + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package io.cardell.openfeature.provider import io.cardell.openfeature.EvaluationContext -import io.cardell.openfeature.StructureDecoder +// import io.cardell.openfeature.StructureDecoder trait Provider[F[_]] { def metadata: ProviderMetadata @@ -12,27 +28,27 @@ trait Provider[F[_]] { context: EvaluationContext ): F[ResolutionDetails[Boolean]] - def resolveStringValue( - flagKey: String, - defaultValue: String, - context: EvaluationContext - ): F[ResolutionDetails[String]] - - def resolveIntValue( - flagKey: String, - defaultValue: Int, - context: EvaluationContext - ): F[ResolutionDetails[Int]] - - def resolveDoubleValue( - flagKey: String, - defaultValue: Double, - context: EvaluationContext - ): F[ResolutionDetails[Double]] - - def resolveStructureValue[A: StructureDecoder]( - flagKey: String, - defaultValue: A, - context: EvaluationContext - ): F[ResolutionDetails[A]] + // def resolveStringValue( + // flagKey: String, + // defaultValue: String, + // context: EvaluationContext + // ): F[ResolutionDetails[String]] + // + // def resolveIntValue( + // flagKey: String, + // defaultValue: Int, + // context: EvaluationContext + // ): F[ResolutionDetails[Int]] + // + // def resolveDoubleValue( + // flagKey: String, + // defaultValue: Double, + // context: EvaluationContext + // ): F[ResolutionDetails[Double]] + // + // def resolveStructureValue[A: StructureDecoder]( + // flagKey: String, + // defaultValue: A, + // context: EvaluationContext + // ): F[ResolutionDetails[A]] } diff --git a/open-feature/sdk/src/main/scala/io/cardell/openfeature/provider/ProviderMetadata.scala b/open-feature/sdk/src/main/scala/io/cardell/openfeature/provider/ProviderMetadata.scala index f7f817c..cb14d4c 100644 --- a/open-feature/sdk/src/main/scala/io/cardell/openfeature/provider/ProviderMetadata.scala +++ b/open-feature/sdk/src/main/scala/io/cardell/openfeature/provider/ProviderMetadata.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2023 Alex Cardell + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package io.cardell.openfeature.provider case class ProviderMetadata(name: String) diff --git a/open-feature/sdk/src/main/scala/io/cardell/openfeature/provider/ProviderStatus.scala b/open-feature/sdk/src/main/scala/io/cardell/openfeature/provider/ProviderStatus.scala index c536e2c..0fc466b 100644 --- a/open-feature/sdk/src/main/scala/io/cardell/openfeature/provider/ProviderStatus.scala +++ b/open-feature/sdk/src/main/scala/io/cardell/openfeature/provider/ProviderStatus.scala @@ -1,3 +1,19 @@ +/* + * Copyright 2023 Alex Cardell + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package io.cardell.openfeature.provider sealed trait ProviderStatus @@ -29,4 +45,3 @@ object ProviderStatus { // client-side only // case object Reconciling extends ProviderStatus } - diff --git a/open-feature/sdk/src/main/scala/io/cardell/openfeature/provider/ResolutionDetails.scala b/open-feature/sdk/src/main/scala/io/cardell/openfeature/provider/ResolutionDetails.scala index e3f133b..b1bb9ce 100644 --- a/open-feature/sdk/src/main/scala/io/cardell/openfeature/provider/ResolutionDetails.scala +++ b/open-feature/sdk/src/main/scala/io/cardell/openfeature/provider/ResolutionDetails.scala @@ -1,15 +1,31 @@ +/* + * Copyright 2023 Alex Cardell + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package io.cardell.openfeature.provider -import io.cardell.openfeature.EvaluationReason import io.cardell.openfeature.ErrorCode +import io.cardell.openfeature.EvaluationReason sealed trait FlagMetadataValue object FlagMetadataValue { case class Boolean(value: Boolean) extends FlagMetadataValue - case class String(value: String) extends FlagMetadataValue - case class Int(value: String) extends FlagMetadataValue - case class Double(value: Double) extends FlagMetadataValue + case class String(value: String) extends FlagMetadataValue + case class Int(value: String) extends FlagMetadataValue + case class Double(value: Double) extends FlagMetadataValue // TODO circe unwrapped codecs } diff --git a/open-feature/sdk/src/main/scala/io/cardell/openfeature/provider/package.scala b/open-feature/sdk/src/main/scala/io/cardell/openfeature/provider/package.scala index 9b6003a..f0b1b98 100644 --- a/open-feature/sdk/src/main/scala/io/cardell/openfeature/provider/package.scala +++ b/open-feature/sdk/src/main/scala/io/cardell/openfeature/provider/package.scala @@ -1,7 +1,23 @@ +/* + * Copyright 2023 Alex Cardell + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package io.cardell.openfeature package object provider { type FlagMetadata = Map[String, FlagMetadataValue] - + } diff --git a/project/plugins.sbt b/project/plugins.sbt index d313796..fcd01cd 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,5 +1,5 @@ -addSbtPlugin("org.typelevel" % "sbt-typelevel" % "0.6.7") -addSbtPlugin("org.typelevel" % "sbt-typelevel-site" % "0.6.7") -addSbtPlugin("org.typelevel" % "sbt-typelevel-scalafix" % "0.6.7") -addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.16.0") -addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.4.17") +addSbtPlugin("org.typelevel" % "sbt-typelevel" % "0.7.3") +addSbtPlugin("org.typelevel" % "sbt-typelevel-site" % "0.7.3") +addSbtPlugin("org.typelevel" % "sbt-typelevel-scalafix" % "0.7.3") +addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.16.0") +addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.4.17")