Skip to content

Commit

Permalink
sdk-trace: add StatusData
Browse files Browse the repository at this point in the history
  • Loading branch information
iRevive committed Nov 10, 2023
1 parent 6c873d1 commit 291cb56
Show file tree
Hide file tree
Showing 2 changed files with 169 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* Copyright 2023 Typelevel
*
* 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 org.typelevel.otel4s.sdk.trace.data

import cats.Hash
import cats.Show
import cats.syntax.show._
import org.typelevel.otel4s.trace.Status

/** Defines the status of a span by providing a standard status in conjunction
* with an optional description message.
*
* @see
* [[https://opentelemetry.io/docs/specs/otel/trace/api/#set-status]]
*/
sealed trait StatusData {

/** The status code.
*/
def status: Status

/** The description of this status for human consumption.
*/
def description: Option[String]

override final def hashCode(): Int =
Hash[StatusData].hash(this)

override final def equals(obj: Any): Boolean =
obj match {
case other: StatusData => Hash[StatusData].eqv(this, other)
case _ => false
}

override final def toString: String =
Show[StatusData].show(this)
}

object StatusData {

/** Indicates the successfully completed operation, validated by an
* application developer or operator.
*/
val Ok: StatusData = StatusDataImpl(Status.Ok, None)

/** The default state.
*/
val Unset: StatusData = StatusDataImpl(Status.Unset, None)

/** Indicates an occurred error.
*/
val Error: StatusData = StatusDataImpl(Status.Error, None)

/** Returns [[StatusData]] for the given `status`.
*/
def apply(status: Status): StatusData =
status match {
case Status.Ok => Ok
case Status.Unset => Unset
case Status.Error => Error
}

/** Creates [[StatusData]] using the given `status` and `description`.
*
* @param status
* the status of the [[StatusData]]
*
* @param description
* the description of the [[StatusData]]
*/
def apply(status: Status, description: String): StatusData =
if (description.isEmpty) apply(status)
else StatusDataImpl(status, Some(description))

implicit val statusDataHash: Hash[StatusData] =
Hash.by(a => (a.status, a.description))

implicit val statusDataShow: Show[StatusData] = Show.show { data =>
show"StatusData{status=${data.status}, description=${data.description}}"
}

private final case class StatusDataImpl(
status: Status,
description: Option[String]
) extends StatusData

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package org.typelevel.otel4s.sdk.trace.data

import cats.Show
import cats.kernel.laws.discipline.HashTests
import cats.syntax.show._
import munit.DisciplineSuite
import org.scalacheck.Arbitrary
import org.scalacheck.Cogen
import org.scalacheck.Gen
import org.scalacheck.Prop
import org.typelevel.otel4s.trace.Status

class StatusDataSuite extends DisciplineSuite {

private val statusGen: Gen[Status] =
Gen.oneOf(Status.Ok, Status.Error, Status.Unset)

private val statusDataGen: Gen[StatusData] =
for {
status <- statusGen
description <- Gen.alphaNumStr
} yield StatusData(status, if (status == Status.Error) description else "")

private implicit val statusDataArbitrary: Arbitrary[StatusData] =
Arbitrary(statusDataGen)

private implicit val statusCogen: Cogen[Status] =
Cogen[String].contramap(_.toString)

private implicit val traceStateCogen: Cogen[StatusData] =
Cogen[(Status, Option[String])].contramap(s => (s.status, s.description))

checkAll("StatusData.HashLaws", HashTests[StatusData].hash)

test("Show[StatusData]") {
Prop.forAll(statusDataGen) { data =>
val expected =
show"StatusData{status=${data.status}, description=${data.description}}"

assertEquals(Show[StatusData].show(data), expected)
}
}

test("use const instances when the given description is empty") {
Prop.forAll(statusGen) { status =>
val expected = status match {
case Status.Ok => StatusData.Ok
case Status.Error => StatusData.Error
case Status.Unset => StatusData.Unset
}

assertEquals(StatusData(status), expected)
assertEquals(StatusData(status, ""), expected)
}
}

test("defaults have empty description") {
val all = Seq(
StatusData.Ok,
StatusData.Error,
StatusData.Unset
)

all.foreach { statusData =>
assertEquals(statusData.description, None)
}
}
}

0 comments on commit 291cb56

Please sign in to comment.