Skip to content

Commit 143b3ca

Browse files
authored
Merge pull request #461 from softwaremill/attributes
Add type-safe attributes implementation
2 parents f94a711 + b134f21 commit 143b3ca

File tree

6 files changed

+90
-1
lines changed

6 files changed

+90
-1
lines changed

build.sbt

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,15 @@ lazy val rootProject = (project in file("."))
9090
.aggregate(projectAggregates: _*)
9191

9292
lazy val core = (projectMatrix in file("core"))
93-
.settings(name := "core")
93+
.settings(
94+
name := "core",
95+
libraryDependencies ++= {
96+
CrossVersion.partialVersion(scalaVersion.value) match {
97+
case Some((3, _)) => Nil
98+
case _ => Seq("org.scala-lang" % "scala-reflect" % scalaVersion.value % Provided)
99+
}
100+
}
101+
)
94102
.jvmPlatform(
95103
scalaVersions = scala2 ++ scala3,
96104
settings = commonJvmSettings
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package sttp.attributes
2+
3+
import scala.reflect.macros.blackbox
4+
5+
private[attributes] object AttributeKeyMacro {
6+
def apply[T: c.WeakTypeTag](c: blackbox.Context): c.Expr[AttributeKey[T]] = {
7+
import c.universe._
8+
c.Expr[AttributeKey[T]](
9+
q"new _root_.sttp.tapir.AttributeKey(${c.universe.show(implicitly[c.WeakTypeTag[T]].tpe)})"
10+
)
11+
}
12+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package sttp.attributes
2+
3+
trait AttributeKeyMacros {
4+
5+
/** Create an attribute key which contains the full type name of the given type, as inferred by the macro. */
6+
def apply[T]: AttributeKey[T] = macro AttributeKeyMacro[T]
7+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package sttp.attributes
2+
3+
import scala.quoted.*
4+
5+
trait AttributeKeyMacros {
6+
7+
/** Create an attribute key which contains the full type name of the given type, as inferred by the macro. */
8+
inline def apply[T]: AttributeKey[T] = ${ AttributeKeyMacros[T] }
9+
}
10+
11+
private[attributes] object AttributeKeyMacros {
12+
def apply[T: Type](using q: Quotes): Expr[AttributeKey[T]] = {
13+
import quotes.reflect.*
14+
val t = TypeRepr.of[T]
15+
16+
'{ new AttributeKey[T](${ Expr(t.show) }) }
17+
}
18+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package sttp.attributes
2+
3+
/** A key to be used in an [[AttributeMap]]. The key typically stores the full type name of the value of the attribute.
4+
* Keys can be conveniently created using the `AttributeKey[T]` macro in the companion object.
5+
*
6+
* @param typeName
7+
* The fully qualified name of `T`.
8+
* @tparam T
9+
* Type of the value of the attribute.
10+
*/
11+
class AttributeKey[T](val typeName: String) {
12+
override def equals(other: Any): Boolean = other match {
13+
case that: AttributeKey[_] => typeName == that.typeName
14+
case _ => false
15+
}
16+
17+
override def hashCode(): Int = typeName.hashCode
18+
}
19+
20+
object AttributeKey extends AttributeKeyMacros
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package sttp.attributes
2+
3+
/** A type-safe map from [[AttributeKey]] to values of types determined by the key.
4+
*
5+
* An attribute is arbitrary data that is attached to some top-level sttp-defined objects. The data is not interpreted
6+
* by the libraries in any way, and is passed as-is. However, built-in or user-provided extensions and integrations
7+
* might use the attributes to provide their functionality.
8+
*
9+
* Typically, you'll add attributes using an `.attribute` method on the sttp-defined object. Each attribute should
10+
* correspond to a type. The type should be defined by the extension, which uses the attribute, and make it available
11+
* for import.
12+
*/
13+
case class AttributeMap(private val storage: Map[String, Any]) {
14+
def get[T](k: AttributeKey[T]): Option[T] = storage.get(k.typeName).asInstanceOf[Option[T]]
15+
def put[T](k: AttributeKey[T], v: T): AttributeMap = copy(storage = storage + (k.typeName -> v))
16+
def remove[T](k: AttributeKey[T]): AttributeMap = copy(storage = storage - k.typeName)
17+
18+
def isEmpty: Boolean = storage.isEmpty
19+
def nonEmpty: Boolean = storage.nonEmpty
20+
}
21+
22+
object AttributeMap {
23+
val Empty: AttributeMap = AttributeMap(Map.empty)
24+
}

0 commit comments

Comments
 (0)