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

[Do not merge] core-common: add KeySelect.projection #912

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions core/common/src/main/scala/org/typelevel/otel4s/Attribute.scala
Original file line number Diff line number Diff line change
Expand Up @@ -77,14 +77,22 @@ object Attribute {
* val boolSeqAttribute: Attribute[Seq[Boolean]] = Attribute("key", Seq(false))
* }}}
*
* @example
* a projected attribute type:
* {{{
* case class UserId(id: Int)
* implicit val userIdKeySelect: KeySelect.Projection[UserId, Long] = KeySelect.projection(_.id.toLong)
* val attribute = Attribute("key", UserId(1)) // the derived type is `Attribute[Long]`
* }}}
*
* @param name
* the key name of an attribute
*
* @param value
* the value of an attribute
*/
def apply[A: AttributeKey.KeySelect](name: String, value: A): Attribute[A] =
Impl(AttributeKey.KeySelect[A].make(name), value)
def apply[A](name: String, value: A)(implicit select: AttributeKey.KeySelect[A]): Attribute[select.Out] =
Impl(select.make(name), select.get(value))

implicit val showAttribute: Show[Attribute[_]] = (a: Attribute[_]) => s"${show"${a.key}"}=${a.value}"

Expand Down
84 changes: 62 additions & 22 deletions core/common/src/main/scala/org/typelevel/otel4s/AttributeKey.scala
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,11 @@ import cats.syntax.show._
* the type of value that can be set with the key
*/
sealed trait AttributeKey[A] {

/** The name of the attribute key. */
def name: String

/** The type of the attribute value. */
def `type`: AttributeType[A]

/** @return
Expand Down Expand Up @@ -69,37 +73,73 @@ Could not find the `KeySelect` for ${A}. The `KeySelect` is defined for the foll
String, Boolean, Long, Double, Seq[String], Seq[Boolean], Seq[Long], Seq[Double].
""")
sealed trait KeySelect[A] {
def make(name: String): AttributeKey[A]
type Out
def make(name: String): AttributeKey[Out]
def get(a: A): Out
}

object KeySelect {
def apply[A](implicit ev: KeySelect[A]): KeySelect[A] = ev

implicit val stringKey: KeySelect[String] = instance(AttributeKey.string)
implicit val booleanKey: KeySelect[Boolean] = instance(AttributeKey.boolean)
implicit val longKey: KeySelect[Long] = instance(AttributeKey.long)
implicit val doubleKey: KeySelect[Double] = instance(AttributeKey.double)

implicit val stringSeqKey: KeySelect[Seq[String]] =
instance(AttributeKey.stringSeq)

implicit val booleanSeqKey: KeySelect[Seq[Boolean]] =
instance(AttributeKey.booleanSeq)

implicit val longSeqKey: KeySelect[Seq[Long]] =
instance(AttributeKey.longSeq)

implicit val doubleSeqKey: KeySelect[Seq[Double]] =
instance(AttributeKey.doubleSeq)
/** An `KeySelect` that has identical attribute value and attribute key types.
*
* @tparam A
* the type of the attribute value that can be used with this key, one of [[AttributeType]]
*/
sealed trait Id[A] extends KeySelect[A] {
type Out = A
final def get(a: A): A = a
}

/** A projected instance of the `KeySelect`.
*
* @tparam A
* the type of the attribute value that can be used with this key
*
* @tparam Key
* the type of the attribute key, one of [[AttributeType]]
*/
sealed trait Projection[A, Key] extends KeySelect[A] {
type Out = Key
}

/** The lense instance allows using custom types as an attribute input.
*
* @example
* {{{
* case class UserId(id: Int)
* implicit val userIdKeySelect: KeySelect.Projection[UserId, Long] = KeySelect.projection(_.id.toLong)
* val attribute = Attribute("key", UserId(1)) // the derived type is `Attribute[Long]`
* }}}
*
* @tparam A
* the type of the attribute value
*
* @tparam Key
* the type of the attribute key, one of [[AttributeType]]
*/
def projection[A, Key](f: A => Key)(implicit select: KeySelect.Id[Key]): KeySelect.Projection[A, Key] =
new Projection[A, Key] {
def make(name: String): AttributeKey[Out] = select.make(name)
def get(a: A): Key = f(a)
}

private def instance[A](f: String => AttributeKey[A]): KeySelect[A] =
new KeySelect[A] {
implicit val stringKey: KeySelect.Id[String] = instance(AttributeKey.string)
implicit val booleanKey: KeySelect.Id[Boolean] = instance(AttributeKey.boolean)
implicit val longKey: KeySelect.Id[Long] = instance(AttributeKey.long)
implicit val doubleKey: KeySelect.Id[Double] = instance(AttributeKey.double)
implicit val stringSeqKey: KeySelect.Id[Seq[String]] = instance(AttributeKey.stringSeq)
implicit val booleanSeqKey: KeySelect.Id[Seq[Boolean]] = instance(AttributeKey.booleanSeq)
implicit val longSeqKey: KeySelect.Id[Seq[Long]] = instance(AttributeKey.longSeq)
implicit val doubleSeqKey: KeySelect.Id[Seq[Double]] = instance(AttributeKey.doubleSeq)

private def instance[A](f: String => AttributeKey[A]): KeySelect.Id[A] =
new KeySelect.Id[A] {
def make(name: String): AttributeKey[A] = f(name)
}
}

def apply[A: KeySelect](name: String): AttributeKey[A] =
KeySelect[A].make(name)
def apply[A](name: String)(implicit select: KeySelect[A]): AttributeKey[select.Out] =
select.make(name)

def string(name: String): AttributeKey[String] =
new Impl(name, AttributeType.String)
Expand Down
10 changes: 5 additions & 5 deletions core/common/src/main/scala/org/typelevel/otel4s/Attributes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ sealed trait Attributes
/** @return
* an [[`Attribute`]] matching the given attribute name and type, or `None` if not found.
*/
final def get[T: KeySelect](name: String): Option[Attribute[T]] =
get(KeySelect[T].make(name))
final def get[T](name: String)(implicit k: KeySelect[T]): Option[Attribute[k.Out]] =
get(k.make(name))

/** @return
* an [[`Attribute`]] matching the given attribute key, or `None` if not found
Expand Down Expand Up @@ -204,9 +204,9 @@ object Attributes extends SpecificIterableFactory[Attribute[_], Attributes] {
* @param value
* the value of the attribute
*/
def addOne[A: KeySelect](name: String, value: A): this.type = {
val key = KeySelect[A].make(name)
builder.addOne(key.name -> Attribute(key, value))
def addOne[A](name: String, value: A)(implicit k: KeySelect[A]): this.type = {
val att = Attribute[A](name, value)
builder.addOne(att.key.name -> att)
this
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.typelevel.otel4s

import munit.FunSuite

class AttributeSuite extends FunSuite {

private case class UserId(id: String)
private implicit val userIdKeySelect: AttributeKey.KeySelect.Projection[UserId, String] =
AttributeKey.KeySelect.projection(_.id)

test("use projected KeySelect to derive a type of an attribute") {
val stringAttribute = Attribute("user.id", "123")
val liftedAttribute = Attribute("user.id", UserId("123"))

assertEquals(stringAttribute, liftedAttribute)
}

test("use projected KeySelect to get an attribute from a collection") {
val attributes = Attributes(Attribute("user.id", UserId("1")))

assertEquals(attributes.get[UserId]("user.id"), attributes.get[String]("user.id"))
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,11 @@ trait Gens {
implicit def seqArb[A: Arbitrary]: Arbitrary[Seq[A]] =
Arbitrary(Gen.nonEmptyListOf(Arbitrary.arbitrary[A]))

def attribute[A: AttributeKey.KeySelect: Arbitrary]: Gen[Attribute[A]] =
def attribute[A: AttributeKey.KeySelect.Id: Arbitrary]: Gen[Attribute[A]] =
for {
key <- nonEmptyString
value <- Arbitrary.arbitrary[A]
} yield Attribute(key, value)
} yield Attribute(name = key, value)

val string: Gen[Attribute[String]] = attribute[String]
val boolean: Gen[Attribute[Boolean]] = attribute[Boolean]
Expand Down
Loading