Skip to content

Commit

Permalink
Remove MemoryFlagState for FlagValue (#34)
Browse files Browse the repository at this point in the history
- **Replace MemoryFlagState type with existing FlagValue**
- **Extract FlagValue and FlagValueType files**
- **Add Structure MemoryProvider implementation**
  • Loading branch information
alexcardell authored Sep 15, 2024
1 parent a5cd63b commit 6c5042f
Show file tree
Hide file tree
Showing 6 changed files with 172 additions and 99 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,26 +24,18 @@ import cats.syntax.all._
import io.cardell.openfeature.ErrorCode
import io.cardell.openfeature.EvaluationContext
import io.cardell.openfeature.EvaluationReason
import io.cardell.openfeature.FlagValue
import io.cardell.openfeature.StructureDecoder
import io.cardell.openfeature.provider.EvaluationProvider
import io.cardell.openfeature.provider.ProviderMetadata
import io.cardell.openfeature.provider.ResolutionDetails

sealed trait MemoryFlagState

object MemoryFlagState {
case class BooleanFlagState(value: Boolean) extends MemoryFlagState
case class StringFlagState(value: String) extends MemoryFlagState
case class IntFlagState(value: Int) extends MemoryFlagState
case class DoubleFlagState(value: Double) extends MemoryFlagState
}

/** Probably don't use in production, see `resolveStructureValue` for why
*/
final class MemoryProvider[F[_]: MonadThrow](
ref: Ref[F, Map[String, MemoryFlagState]]
ref: Ref[F, Map[String, FlagValue]]
) extends EvaluationProvider[F] {

import MemoryFlagState._

override def metadata: ProviderMetadata = ProviderMetadata("memory")

private def missing[A](
Expand Down Expand Up @@ -86,8 +78,8 @@ final class MemoryProvider[F[_]: MonadThrow](
): F[ResolutionDetails[Boolean]] = ref.get.map { state =>
state.get(flagKey) match {
case None => missing[Boolean](flagKey, defaultValue)
case Some(BooleanFlagState(value)) => resolution[Boolean](value)
case Some(_) => typeMismatch(flagKey, defaultValue)
case Some(FlagValue.BooleanValue(value)) => resolution[Boolean](value)
case Some(_) => typeMismatch(flagKey, defaultValue)
}
}

Expand All @@ -98,8 +90,8 @@ final class MemoryProvider[F[_]: MonadThrow](
): F[ResolutionDetails[String]] = ref.get.map { state =>
state.get(flagKey) match {
case None => missing[String](flagKey, defaultValue)
case Some(StringFlagState(value)) => resolution[String](value)
case Some(_) => typeMismatch(flagKey, defaultValue)
case Some(FlagValue.StringValue(value)) => resolution[String](value)
case Some(_) => typeMismatch(flagKey, defaultValue)
}
}

Expand All @@ -109,9 +101,9 @@ final class MemoryProvider[F[_]: MonadThrow](
context: EvaluationContext
): F[ResolutionDetails[Int]] = ref.get.map { state =>
state.get(flagKey) match {
case None => missing[Int](flagKey, defaultValue)
case Some(IntFlagState(value)) => resolution[Int](value)
case Some(_) => typeMismatch(flagKey, defaultValue)
case None => missing[Int](flagKey, defaultValue)
case Some(FlagValue.IntValue(value)) => resolution[Int](value)
case Some(_) => typeMismatch(flagKey, defaultValue)
}
}

Expand All @@ -122,44 +114,43 @@ final class MemoryProvider[F[_]: MonadThrow](
): F[ResolutionDetails[Double]] = ref.get.map { state =>
state.get(flagKey) match {
case None => missing[Double](flagKey, defaultValue)
case Some(DoubleFlagState(value)) => resolution[Double](value)
case Some(_) => typeMismatch(flagKey, defaultValue)
case Some(FlagValue.DoubleValue(value)) => resolution[Double](value)
case Some(_) => typeMismatch(flagKey, defaultValue)
}
}

/** NOTE: StructureValue can contain anything, and therefore may throw
* classcast exceptions
*
* Can't get around type erasure to do the check
*/
override def resolveStructureValue[A: StructureDecoder](
flagKey: String,
defaultValue: A,
context: EvaluationContext
): F[ResolutionDetails[A]] = MonadThrow[F].raiseError(
new NotImplementedError(
"Structure values not implemented in in-memory provider"
)
)
// {
// val resolved = ref.get.map { state =>
// val x = state.get(flagKey) match {
// case None => missing[A](flagKey, defaultValue)
// case Some(StructureFlagState(value)) => resolution[A](value.asInstanceOf[A])
// case Some(_) => typeMismatch(flagKey, defaultValue)
// }
// @nowarn
// val value: A = x.value
// x
// }
//
// resolved.handleError { case _ => missing[A](flagKey, defaultValue) }
// }
): F[ResolutionDetails[A]] = {
val resolved = ref.get.map { state =>
state.get(flagKey) match {
case None => missing[A](flagKey, defaultValue)
case Some(FlagValue.StructureValue(value)) =>
val v = value.asInstanceOf[A]
resolution[A](v)
case Some(_) => typeMismatch(flagKey, defaultValue)
}
}

resolved.handleError { case _ => missing[A](flagKey, defaultValue) }
}

}

object MemoryProvider {

def apply[F[_]: Sync](
state: Map[String, MemoryFlagState]
state: Map[String, FlagValue]
): F[MemoryProvider[F]] =
for {
ref <- Ref.of[F, Map[String, MemoryFlagState]](state)
ref <- Ref.of[F, Map[String, FlagValue]](state)
} yield new MemoryProvider[F](ref)

}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import munit.CatsEffectSuite

import io.cardell.openfeature.ErrorCode
import io.cardell.openfeature.EvaluationContext
import io.cardell.openfeature.FlagValue
import io.cardell.openfeature.StructureDecoder
import io.cardell.openfeature.StructureDecoderError

Expand All @@ -41,7 +42,7 @@ class MemoryProviderTest extends CatsEffectSuite {
test("can return boolean values") {
val expected = true

val flag = MemoryFlagState.BooleanFlagState(expected)
val flag = FlagValue.BooleanValue(expected)
val key = "boolean-flag-key"
val state = Map(key -> flag)

Expand All @@ -63,7 +64,7 @@ class MemoryProviderTest extends CatsEffectSuite {
test("can return string values") {
val expected = "string"

val flag = MemoryFlagState.StringFlagState(expected)
val flag = FlagValue.StringValue(expected)
val key = "string-flag-key"
val state = Map(key -> flag)

Expand All @@ -85,7 +86,7 @@ class MemoryProviderTest extends CatsEffectSuite {
test("can return int values when type is as expected") {
val expected = 33

val flag = MemoryFlagState.IntFlagState(expected)
val flag = FlagValue.IntValue(expected)
val key = "int-flag-key"
val state = Map(key -> flag)

Expand All @@ -107,7 +108,7 @@ class MemoryProviderTest extends CatsEffectSuite {
test("can return double values when type is as expected") {
val expected = 40.0

val flag = MemoryFlagState.DoubleFlagState(expected)
val flag = FlagValue.DoubleValue(expected)
val key = "double-flag-key"
val state = Map(key -> flag)

Expand All @@ -126,11 +127,33 @@ class MemoryProviderTest extends CatsEffectSuite {
}
}

test("can return structure values") {
val expected = TestStructure("a", 0)

val flag = FlagValue.StructureValue(expected)
val key = "structure-flag-key"
val state = Map(key -> flag)

val default = TestStructure("a", 0)

MemoryProvider[IO](state).flatMap { provider =>
val resolution = provider.resolveStructureValue[TestStructure](
key,
default,
EvaluationContext.empty
)

for {
result <- resolution.map(_.value)
} yield assertEquals(result, expected)
}
}

test("receives type mismatch error when boolean not received") {
val expectedValue = false
val expectedErrorCode = Some(ErrorCode.TypeMismatch)

val flag = MemoryFlagState.DoubleFlagState(0.0)
val flag = FlagValue.DoubleValue(0.0)
val key = "boolean-flag-key"
val state = Map(key -> flag)

Expand Down Expand Up @@ -158,7 +181,7 @@ class MemoryProviderTest extends CatsEffectSuite {
val expectedValue = "default"
val expectedErrorCode = Some(ErrorCode.TypeMismatch)

val flag = MemoryFlagState.DoubleFlagState(0.0)
val flag = FlagValue.DoubleValue(0.0)
val key = "string-flag-key"
val state = Map(key -> flag)

Expand Down Expand Up @@ -186,7 +209,7 @@ class MemoryProviderTest extends CatsEffectSuite {
val expectedValue = 33
val expectedErrorCode = Some(ErrorCode.TypeMismatch)

val flag = MemoryFlagState.DoubleFlagState(0.0)
val flag = FlagValue.DoubleValue(0.0)
val key = "int-flag-key"
val state = Map(key -> flag)

Expand Down Expand Up @@ -214,7 +237,7 @@ class MemoryProviderTest extends CatsEffectSuite {
val expectedValue = 40.0
val expectedErrorCode = Some(ErrorCode.TypeMismatch)

val flag = MemoryFlagState.IntFlagState(0)
val flag = FlagValue.IntValue(0)
val key = "double-flag-key"
val state = Map(key -> flag)

Expand All @@ -238,4 +261,29 @@ class MemoryProviderTest extends CatsEffectSuite {
}
}

// test(
// "receives type mismatch error when expected structure type not received"
// ) {
// val expected = TestStructure("a", 0)
//
// val flag = FlagValue.StructureValue(OtherTestStructure(40.0))
// val key = "structure-flag-key"
// val state = Map(key -> flag)
//
// val default = expected
//
// MemoryProvider[IO](state).flatMap { provider =>
// val resolution = provider.resolveStructureValue(
// key,
// default,
// EvaluationContext.empty
// )
//
// for {
// result <- resolution
// _ <- IO.println("alexxxxxx")
// } yield assertEquals(result.value, expected)
// }
// }

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* 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.FlagValue.BooleanValue
import io.cardell.openfeature.FlagValue.DoubleValue
import io.cardell.openfeature.FlagValue.IntValue
import io.cardell.openfeature.FlagValue.StringValue
import io.cardell.openfeature.FlagValue.StructureValue

sealed trait FlagValue {

def valueType: FlagValueType =
this match {
case _: BooleanValue => FlagValueType.BooleanValueType
case _: StringValue => FlagValueType.StringValueType
case _: IntValue => FlagValueType.IntValueType
case _: DoubleValue => FlagValueType.DoubleValueType
case _: StructureValue[_] => FlagValueType.StructureValueType
}

}

object FlagValue {
case class BooleanValue(value: Boolean) extends FlagValue
case class StringValue(value: String) extends FlagValue
case class IntValue(value: Int) extends FlagValue
case class DoubleValue(value: Double) extends FlagValue
case class StructureValue[A](value: A) extends FlagValue

def apply(b: Boolean): FlagValue = BooleanValue(b)
def apply(s: String): FlagValue = StringValue(s)
def apply(i: Int): FlagValue = IntValue(i)
def apply(d: Double): FlagValue = DoubleValue(d)
def apply[A](s: A): FlagValue = StructureValue(s)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* 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 FlagValueType

object FlagValueType {
case object BooleanValueType extends FlagValueType
case object StringValueType extends FlagValueType
case object IntValueType extends FlagValueType
case object DoubleValueType extends FlagValueType
case object StructureValueType extends FlagValueType
}
Loading

0 comments on commit 6c5042f

Please sign in to comment.