Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

OpenFeature: Contstrain StructureValue to Map[String, FlagValue] #36

Merged
merged 15 commits into from
Sep 20, 2024
8 changes: 4 additions & 4 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import build.V
Global / onChangedBuildSource := ReloadOnSourceChanges

// https://typelevel.org/sbt-typelevel/faq.html#what-is-a-base-version-anyway
ThisBuild / tlBaseVersion := "0.3" // your current series x.y
ThisBuild / tlBaseVersion := "0.4" // your current series x.y

ThisBuild / organization := "io.cardell"
ThisBuild / organizationName := "Alex Cardell"
Expand Down Expand Up @@ -140,7 +140,8 @@ lazy val `openfeature-provider-flipt` = crossProject(
name := "openfeature-provider-flipt"
)
.dependsOn(
`openfeature-sdk`,
// `openfeature-sdk`,
`openfeature-sdk-circe`,
`flipt-sdk-server`
)

Expand All @@ -158,8 +159,7 @@ lazy val `openfeature-provider-flipt-it` = crossProject(JVMPlatform)
)
)
.dependsOn(
`openfeature-provider-flipt`,
`openfeature-sdk-circe`
`openfeature-provider-flipt`
)

lazy val examples = crossProject(JVMPlatform)
Expand Down
118 changes: 59 additions & 59 deletions docker/flipt/features.yaml
Original file line number Diff line number Diff line change
@@ -1,64 +1,64 @@
version: "1.2"
namespace: default
flags:
- key: boolean-flag-1
name: boolean-flag-1
type: BOOLEAN_FLAG_TYPE
enabled: true
- key: variant-flag-1
name: variant-flag-1
type: VARIANT_FLAG_TYPE
enabled: true
variants:
- key: key-1
attachment:
field: string
intField: 33
rules:
- segment: default-ns-segment-1
distributions:
- variant: key-1
rollout: 100
- key: string-variant-flag-1
name: string-variant-flag-1
type: VARIANT_FLAG_TYPE
enabled: true
variants:
- key: string-variant-1
rules:
- segment: default-ns-segment-1
distributions:
- variant: string-variant-1
rollout: 100
- key: int-variant-flag-1
name: int-variant-flag-1
type: VARIANT_FLAG_TYPE
enabled: true
variants:
- key: '13'
rules:
- segment: default-ns-segment-1
distributions:
- variant: '13'
rollout: 100
- key: double-variant-flag-1
name: double-variant-flag-1
type: VARIANT_FLAG_TYPE
enabled: true
variants:
- key: '17.1'
rules:
- segment: default-ns-segment-1
distributions:
- variant: '17.1'
rollout: 100
- key: boolean-flag-1
name: boolean-flag-1
type: BOOLEAN_FLAG_TYPE
enabled: true
- key: variant-flag-1
name: variant-flag-1
type: VARIANT_FLAG_TYPE
enabled: true
variants:
- key: key-1
attachment:
field: string
intField: 33
rules:
- segment: default-ns-segment-1
distributions:
- variant: key-1
rollout: 100
- key: string-variant-flag-1
name: string-variant-flag-1
type: VARIANT_FLAG_TYPE
enabled: true
variants:
- key: string-variant-1
rules:
- segment: default-ns-segment-1
distributions:
- variant: string-variant-1
rollout: 100
- key: int-variant-flag-1
name: int-variant-flag-1
type: VARIANT_FLAG_TYPE
enabled: true
variants:
- key: '13'
rules:
- segment: default-ns-segment-1
distributions:
- variant: '13'
rollout: 100
- key: double-variant-flag-1
name: double-variant-flag-1
type: VARIANT_FLAG_TYPE
enabled: true
variants:
- key: '17.1'
rules:
- segment: default-ns-segment-1
distributions:
- variant: '17.1'
rollout: 100

segments:
- key: default-ns-segment-1
name: default-ns-segment-1
constraints:
- type: STRING_COMPARISON_TYPE
property: test-property
operator: eq
value: matched-property-value
match_type: ALL_MATCH_TYPE
- key: default-ns-segment-1
name: default-ns-segment-1
constraints:
- type: STRING_COMPARISON_TYPE
property: test-property
operator: eq
value: matched-property-value
match_type: ALL_MATCH_TYPE
3 changes: 2 additions & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ See `Flipt usage` on how to set up the `FliptApi`. Once done, set up a provider:
```scala mdoc
import cats.effect.IO
import io.circe.Decoder
import io.circe.Encoder

import io.cardell.flipt.FliptApi
import io.cardell.openfeature.OpenFeature
Expand All @@ -53,7 +54,7 @@ import io.cardell.openfeature.circe._

case class SomeVariant(field: String, field2: Int)

def provider(flipt: FliptApi[IO])(implicit d: Decoder[SomeVariant]) = {
def provider(flipt: FliptApi[IO])(implicit d: Decoder[SomeVariant], e: Encoder.AsObject[SomeVariant]) = {
val featureSdk = OpenFeature[IO](new FliptProvider[IO](flipt, "some-namespace"))

featureSdk.client.flatMap { featureClient =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import io.cardell.flipt.FliptApi
import io.cardell.flipt.auth.AuthenticationStrategy
import io.cardell.openfeature.ContextValue
import io.cardell.openfeature.EvaluationContext
import io.cardell.openfeature.StructureCodec._
import io.cardell.openfeature.circe._

// see docker-compose features.yaml for flag test data
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,13 @@
package io.cardell.openfeature.provider.flipt

import io.circe.Decoder
import io.circe.Encoder
import io.circe.generic.semiauto.deriveDecoder
import io.circe.generic.semiauto.deriveEncoder

case class TestVariant(field: String, intField: Int)

object TestVariant {
implicit val decoder: Decoder[TestVariant] = deriveDecoder
implicit val decoder: Decoder[TestVariant] = deriveDecoder
implicit val encoder: Encoder.AsObject[TestVariant] = deriveEncoder
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package io.cardell.openfeature.provider.flipt

import cats.MonadThrow
import cats.syntax.all._
import io.circe.parser.parse
import scala.util.Success
import scala.util.Try

Expand All @@ -27,8 +28,10 @@ import io.cardell.flipt.model.{EvaluationReason => FliptReason}
import io.cardell.openfeature.ErrorCode
import io.cardell.openfeature.EvaluationContext
import io.cardell.openfeature.EvaluationReason
import io.cardell.openfeature.StructureCodec
import io.cardell.openfeature.StructureDecoder
import io.cardell.openfeature.StructureDecoderError
import io.cardell.openfeature.circe.JsonStructureConverters
import io.cardell.openfeature.provider.EvaluationProvider
import io.cardell.openfeature.provider.FlagMetadataValue
import io.cardell.openfeature.provider.ProviderMetadata
Expand Down Expand Up @@ -103,7 +106,7 @@ final class FliptProvider[F[_]: MonadThrow](
context
)

override def resolveStructureValue[A: StructureDecoder](
override def resolveStructureValue[A: StructureCodec](
flagKey: String,
defaultValue: A,
context: EvaluationContext
Expand All @@ -119,25 +122,24 @@ final class FliptProvider[F[_]: MonadThrow](
)

val resolution = flipt.evaluateVariant(req).map { evaluation =>
val decodedAttachment = StructureDecoder[A]
.decodeStructure(evaluation.variantAttachment)

decodedAttachment match {
case Left(error) => decodeDefault[A](error, defaultValue)
case Right(decoded) =>
ResolutionDetails[A](
value = decoded,
errorCode = None,
errorMessage = None,
reason = mapReason(evaluation.reason).some,
variant = Some(evaluation.variantKey),
metadata = Some(
Map(
"variant-attachment" -> FlagMetadataValue
.StringValue(evaluation.variantAttachment)
)
)
val jsonAttachment = parse(evaluation.variantAttachment).map(_.asObject)

jsonAttachment match {
case Left(parseError) =>
ResolutionDetails.error(defaultValue, parseError)
case Right(None) =>
ResolutionDetails.error(
defaultValue,
new Throwable("did not receive json object")
)
case Right(Some(jsonObject)) =>
val structure = JsonStructureConverters.jsonToStructure(jsonObject)
val decodedStructure = StructureDecoder[A].decodeStructure(structure)
decodedStructure match {
case Left(error) =>
ResolutionDetails.error[A](defaultValue, error.cause)
case Right(value) => ResolutionDetails[A](value)
}
}

}
Expand Down Expand Up @@ -174,7 +176,7 @@ final class FliptProvider[F[_]: MonadThrow](
) = ResolutionDetails[A](
value = defaultValue,
errorCode = Some(ErrorCode.ParseError),
errorMessage = Some(e.getMessage()),
errorMessage = Some(e.message),
reason = Some(EvaluationReason.Error),
variant = None,
metadata = None
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import io.cardell.openfeature.ErrorCode
import io.cardell.openfeature.EvaluationContext
import io.cardell.openfeature.EvaluationReason
import io.cardell.openfeature.FlagValue
import io.cardell.openfeature.StructureCodec
import io.cardell.openfeature.StructureDecoder
import io.cardell.openfeature.provider.EvaluationProvider
import io.cardell.openfeature.provider.ProviderMetadata
Expand Down Expand Up @@ -124,7 +125,7 @@ final class MemoryProvider[F[_]: MonadThrow](
*
* Can't get around type erasure to do the check
*/
override def resolveStructureValue[A: StructureDecoder](
override def resolveStructureValue[A: StructureCodec](
flagKey: String,
defaultValue: A,
context: EvaluationContext
Expand All @@ -133,8 +134,12 @@ final class MemoryProvider[F[_]: MonadThrow](
state.get(flagKey) match {
case None => missing[A](flagKey, defaultValue)
case Some(FlagValue.StructureValue(value)) =>
val v = value.asInstanceOf[A]
resolution[A](v)
val decoded = StructureDecoder[A].decodeStructure(value)

decoded match {
case Right(value) => resolution[A](value)
case Left(_) => typeMismatch[A](flagKey, defaultValue)
}
case Some(_) => typeMismatch(flagKey, defaultValue)
}
}
Expand Down
Loading