Skip to content

Commit

Permalink
Add Attributes#{updated,concat}
Browse files Browse the repository at this point in the history
Add `Attributes#updated` as well as `Attributes#+` alias for it.
Add `Attributes#concat` invariant overload as well as
`Attributes#++` alias for it.
  • Loading branch information
NthPortal committed Jan 3, 2024
1 parent 54a930c commit bcc230c
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 8 deletions.
70 changes: 64 additions & 6 deletions core/common/src/main/scala/org/typelevel/otel4s/Attributes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -38,20 +38,72 @@ sealed trait Attributes

/** Returns an attribute for the given attribute name, or `None` if not found.
*/
def get[T: KeySelect](name: String): Option[Attribute[T]]
final def get[T: KeySelect](name: String): Option[Attribute[T]] =
get(KeySelect[T].make(name))

/** Returns an attribute for the given attribute key, or `None` if not found.
*/
def get[T](key: AttributeKey[T]): Option[Attribute[T]]

/** Whether this attributes collection contains the given key.
/** Whether or not these `Attributes` contain a key with the given name and
* type.
*/
final def contains[T: KeySelect](name: String): Boolean =
contains(KeySelect[T].make(name))

/** Whether or not these `Attributes` contain the given key. */
def contains(key: AttributeKey[_]): Boolean

/** Adds an [[`Attribute`]] with the given name and value to these
* `Attributes`, replacing any `Attribute` with the same name and type if one
* exists.
*/
final def updated[T: KeySelect](name: String, value: T): Attributes =
updated(Attribute(name, value))

/** Adds an [[`Attribute`]] with the given key and value to these
* `Attributes`, replacing any `Attribute` with the same key if one exists.
*/
final def updated[T](key: AttributeKey[T], value: T): Attributes =
updated(Attribute(key, value))

/** Adds the given [[`Attribute`]] to these `Attributes`, replacing any
* `Attribute` with the same key if one exists.
*/
def updated(attribute: Attribute[_]): Attributes

/** Adds the given [[`Attribute`]] to these `Attributes`, replacing any
* `Attribute` with the same key if one exists.
*/
final def +(attribute: Attribute[_]): Attributes =
updated(attribute)

/** Invariant overload of
* [[scala.collection.IterableOps.concat `IterableOps#concat`]] that returns
* `Attributes` rather than `Iterable`.
*
* If multiple [[`Attribute`]]s in `this` and/or `that` have the same key,
* only the final one (according to `that`'s iterator) will be retained in
* the resulting `Attributes`.
*/
def concat(that: IterableOnce[Attribute[_]]): Attributes =
attributesFactory.fromSpecific(this.view ++ that)

/** Invariant overload of [[scala.collection.IterableOps.++ `IterableOps#++`]]
* that returns `Attributes` rather than `Iterable`.
*
* If multiple [[`Attribute`]]s in `this` and/or `that` have the same key,
* only the final one (according to `that`'s iterator) will be retained in
* the resulting `Attributes`.
*/
final def ++(that: IterableOnce[Attribute[_]]): Attributes =
concat(that)

/** Returns the `Map` representation of the attributes collection.
*/
def toMap: Map[AttributeKey[_], Attribute[_]]

/** A factory for creating `Attributes`. */
def attributesFactory: SpecificIterableFactory[Attribute[_], Attributes]

override def empty: Attributes = attributesFactory.empty
Expand Down Expand Up @@ -220,13 +272,19 @@ object Attributes extends SpecificIterableFactory[Attribute[_], Attributes] {
private final class MapAttributes(
private val m: Map[AttributeKey[_], Attribute[_]]
) extends Attributes {
def get[T: KeySelect](name: String): Option[Attribute[T]] =
get(KeySelect[T].make(name))

def get[T](key: AttributeKey[T]): Option[Attribute[T]] =
m.get(key).map(_.asInstanceOf[Attribute[T]])

def contains(key: AttributeKey[_]): Boolean = m.contains(key)
def updated(attribute: Attribute[_]): Attributes =
new MapAttributes(m.updated(attribute.key, attribute))
override def concat(that: IterableOnce[Attribute[_]]): Attributes =
that match {
case other: Attributes =>
new MapAttributes(m ++ other.toMap)
case other =>
new MapAttributes(m ++ other.iterator.map(a => a.key -> a))
}

def toMap: Map[AttributeKey[_], Attribute[_]] = m
def iterator: Iterator[Attribute[_]] = m.valuesIterator

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
package org.typelevel.otel4s

import cats.Show
import cats.syntax.semigroup._
import munit.ScalaCheckSuite
import org.scalacheck.Arbitrary
import org.scalacheck.Gen
Expand Down Expand Up @@ -108,6 +107,28 @@ class AttributesProps extends ScalaCheckSuite {
}
}

property("Attributes#updated adds attributes and replaces existing ones") {
forAll { (value1: String, value2: String) =>
val a1 = Attribute("key", value1)
val a2 = Attribute("key", value2)
val attrs1 = Attributes.empty + a1
val attrs2 = Attributes.empty + a2
val attrs12 = Attributes.empty + a1 + a2

val attrs1Contains = attrs1.get[String]("key").exists(_.value == value1)
val attrs2Contains = attrs2.get[String]("key").exists(_.value == value2)
val attrs12Checks =
attrs12.get[String]("key").exists(_.value == value2) &&
attrs12.sizeIs == 1 &&
(value1 == value2 ||
attrs12
.get[String]("key")
.forall(_.value != value1))

attrs1Contains && attrs2Contains && attrs12Checks
}
}

property("Attributes#++ combines two sets of attributes") {
forAll(listOfAttributes, listOfAttributes) { (attributes1, attributes2) =>
val keySet1 = attributes1.map(_.key).toSet
Expand All @@ -117,7 +138,7 @@ class AttributesProps extends ScalaCheckSuite {
val attrs1 = Attributes(attributes1: _*)
val attrs2 = Attributes(attributes2: _*)

val combined = attrs1 |+| attrs2
val combined = attrs1 ++ attrs2
val sizeIsEqual = combined.size == keySet1.size + keySet2.size

val secondCollectionOverrodeValues = diff.forall { key =>
Expand Down

0 comments on commit bcc230c

Please sign in to comment.