Skip to content

Commit

Permalink
more opaque types and auto-instances
Browse files Browse the repository at this point in the history
  • Loading branch information
ornicar committed Nov 22, 2022
1 parent 95821b6 commit efce0dc
Show file tree
Hide file tree
Showing 24 changed files with 74 additions and 78 deletions.
2 changes: 1 addition & 1 deletion app/controllers/UserAnalysis.scala
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ final class UserAnalysis(
source = lila.game.Source.Api,
pgnImport = None
)
.withId(GameId("synthetic")),
.withId(lila.game.Game.syntheticId),
from.situation.color
)

Expand Down
11 changes: 2 additions & 9 deletions app/ui/scalatags.scala
Original file line number Diff line number Diff line change
Expand Up @@ -120,15 +120,8 @@ trait ScalatagsExtensions:

given Conversion[StringValue, scalatags.Text.Frag] = sv => StringFrag(sv.value)

given AttrValue[GameId] = stringAttrValue
given AttrValue[StudyId] = stringAttrValue
given AttrValue[StudyName] = stringAttrValue

def str[A <: String](a: A): String = a

def stringAttrValue[A <: String]: AttrValue[A] = new AttrValue[A] {
def apply(t: Builder, a: Attr, v: A): Unit = stringAttr(t, a, v: String)
}
given [A](using bts: BasicallyTheSame[A, String]): AttrValue[A] with
def apply(t: Builder, a: Attr, v: A): Unit = stringAttr(t, a, bts(v))

given AttrValue[StringValue] with
def apply(t: Builder, a: Attr, v: StringValue): Unit =
Expand Down
2 changes: 1 addition & 1 deletion app/views/base/embed.scala
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ object embed:
st.body(cls := s"base highlight ${config.board}")(
layout.dataSoundSet := SoundSet.silent.key,
layout.dataAssetUrl,
layout.dataAssetVersion := str(assetVersion),
layout.dataAssetVersion := assetVersion.value,
layout.dataTheme := config.bg,
layout.dataPieceSet := config.pieceSet.name,
body
Expand Down
2 changes: 1 addition & 1 deletion app/views/base/layout.scala
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ object layout:
dataSoundSet := ctx.currentSoundSet.toString,
dataSocketDomains,
dataAssetUrl,
dataAssetVersion := str(assetVersion),
dataAssetVersion := assetVersion,
dataNonce := ctx.nonce.ifTrue(sameAssetDomain).map(_.value),
dataTheme := ctx.currentBg,
dataBoardTheme := ctx.currentTheme.name,
Expand Down
2 changes: 1 addition & 1 deletion app/views/mod/publicChat.scala
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ object publicChat:
h2("Swiss Chats"),
div(cls := "player_chats")(
swissChats.map { case (swiss, chat) =>
div(cls := "game", dataChan := "swiss", dataRoom := str(swiss.id))(
div(cls := "game", dataChan := "swiss", dataRoom := swiss.id)(
chatOf(swissTitle(swiss), chat)
)
}
Expand Down
4 changes: 3 additions & 1 deletion modules/common/src/main/Form.scala
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,9 @@ object Form:

val field = of[URL]

given Formatter[GameId] = formatter.string(GameId.apply)
inline given [A, T](using ev: A =:= T, base: Formatter[A]): Formatter[T] with
def bind(key: String, data: Map[String, String]) = base.bind(key, data) map ev.apply
def unbind(key: String, value: T) = base.unbind(key, ev.flip(value))

given Formatter[chess.variant.Variant] =
formatter.stringFormatter[chess.variant.Variant](_.key, chess.variant.Variant.orDefault)
Expand Down
2 changes: 0 additions & 2 deletions modules/common/src/main/Iso.scala
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,6 @@ object Iso:

def opaque[A <: String](from: String => A): StringIso[A] = apply(from, identity)

given StringIso[StudyId] = opaque(StudyId.apply)

given isoIdentity[A]: Iso[A, A] = apply(identity[A], identity[A])

given StringIso[IpAddress] = string[IpAddress](IpAddress.unchecked, _.value)
Expand Down
5 changes: 3 additions & 2 deletions modules/common/src/main/Json.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ import chess.format.{ FEN, Uci }

object Json:

given Format[GameId] = stringFormat(GameId.apply)
given Format[StudyId] = stringFormat(StudyId.apply)
inline given [A, T](using ev: A =:= T, format: Format[A]): Format[T] =
format.bimap(ev.apply, ev.flip.apply)

given Format[PuzzleId] = stringFormat(PuzzleId.apply)
given Format[Days] = intFormat(Days.apply)

Expand Down
17 changes: 7 additions & 10 deletions modules/common/src/main/base/LilaModel.scala
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
package lila.base

trait LilaModel:
trait LilaModel extends NewTypes:

opaque type GameId <: String = String
object GameId:
def apply(v: String): GameId = v
opaque type GameId = String
object GameId extends OpaqueString[GameId]

opaque type GameFullId <: String = String
object GameFullId:
Expand All @@ -22,13 +21,11 @@ trait LilaModel:
object SwissId:
def apply(v: String): SwissId = v

opaque type StudyId <: String = String
object StudyId:
def apply(v: String): StudyId = v
opaque type StudyId = String
object StudyId extends OpaqueString[StudyId]

opaque type StudyName <: String = String
object StudyName:
def apply(v: String): StudyName = v
opaque type StudyName = String
object StudyName extends OpaqueString[StudyName]

opaque type StudyChapterId <: String = String
object StudyChapterId:
Expand Down
11 changes: 5 additions & 6 deletions modules/common/src/main/model.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,11 @@ opaque type ApiVersion = Int
object ApiVersion extends OpaqueInt[ApiVersion]:
def puzzleV2(v: ApiVersion) = v >= 6

opaque type AssetVersion <: String = String
object AssetVersion:
def apply(v: String): AssetVersion = v
var current = random
def change() = { current = random }
private def random = apply(SecureRandom nextString 6)
opaque type AssetVersion = String
object AssetVersion extends OpaqueString[AssetVersion]:
var current = random
def change() = { current = random }
private def random = AssetVersion(SecureRandom nextString 6)

case class Bearer(secret: String) extends AnyVal:
override def toString = "Bearer(***)"
Expand Down
5 changes: 4 additions & 1 deletion modules/common/src/main/newtypes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ trait NewTypes {
trait TotalWrapper[Newtype, Impl](using ev: Newtype =:= Impl):
def raw(a: Newtype): Impl = ev.apply(a)
def apply(s: Impl): Newtype = ev.flip.apply(s)
given =:=[Newtype, Impl] = ev
given BasicallyTheSame[Newtype, Impl] = ev.apply(_)
given BasicallyTheSame[Impl, Newtype] = ev.flip.apply(_)

Expand All @@ -19,7 +20,7 @@ trait NewTypes {
inline def map(inline f: Impl => Impl): Newtype = apply(f(raw(a)))
end TotalWrapper

inline given [A, T](using bts: BasicallyTheSame[T, A], ord: Ordering[A]): Ordering[T] =
inline def sameOrdering[A, T](using bts: BasicallyTheSame[T, A], ord: Ordering[A]): Ordering[T] =
Ordering.by(bts.apply)

trait OpaqueString[A](using A =:= String) extends TotalWrapper[A, String]
Expand All @@ -37,4 +38,6 @@ trait NewTypes {
inline def value: Boolean = a == Yes
inline def yes: Boolean = a == Yes
end YesNo

given [A]: BasicallyTheSame[A, A] = a => a
}
19 changes: 8 additions & 11 deletions modules/db/src/main/Handlers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@ import scala.collection.Factory

trait Handlers:

inline given [A, T](using ev: A =:= T, handler: BSONHandler[A]): BSONHandler[T] =
handler.as(ev.apply, ev.flip.apply)

given dateTimeHandler: BSONHandler[DateTime] = quickHandler[DateTime](
{ case v: BSONDateTime => new DateTime(v.value) },
v => BSONDateTime(v.getMillis)
)

given BSONHandler[GameId] = stringHandler(GameId.apply)
given BSONHandler[GamePlayerId] = stringHandler(GamePlayerId.apply)
given BSONHandler[StudyId] = stringHandler(StudyId.apply)
given BSONHandler[StudyName] = stringHandler(StudyName.apply)
given BSONHandler[SwissId] = stringHandler(SwissId.apply)
given BSONHandler[TourPlayerId] = stringHandler(TourPlayerId.apply)
given BSONHandler[PuzzleId] = stringHandler(PuzzleId.apply)
Expand Down Expand Up @@ -148,14 +148,11 @@ trait Handlers:
def readTry(bson: BSONValue): Try[List[T]] = reader.readTry(bson)
def writeTry(t: List[T]): Try[BSONValue] = writer.writeTry(t)

// given collectionHandler[T, M[_], Repr <: Iterable[T]](using
// handler: BSONHandler[T],
// factory: Factory[T, M[T]]
// ): BSONHandler[M[T]] with
// val reader = collectionReader[M, T]
// val writer = BSONWriter.collectionWriter[T, Repr]
// def readTry(bson: BSONValue): Try[M[T]] = reader.readTry(bson)
// def writeTry(t: Repr): Try[BSONValue] = writer.writeTry(t)
given vectorHandler[T](using handler: BSONHandler[T]): BSONHandler[Vector[T]] with
val reader = collectionReader[Vector, T]
val writer = BSONWriter.collectionWriter[T, Vector[T]]
def readTry(bson: BSONValue): Try[Vector[T]] = reader.readTry(bson)
def writeTry(t: Vector[T]): Try[BSONValue] = writer.writeTry(t)

given BSONWriter[BSONNull.type] with
def writeTry(n: BSONNull.type) = Success(BSONNull)
Expand Down
11 changes: 8 additions & 3 deletions modules/db/src/main/dsl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -423,12 +423,17 @@ object dsl extends dsl with Handlers:
def list[D: BSONDocumentReader](selector: Bdoc, limit: Int): Fu[List[D]] =
coll.find(selector, none[Bdoc]).cursor[D]().list(limit = limit)

def byId[D: BSONDocumentReader, I: BSONWriter](id: I): Fu[Option[D]] =
def byTypedId[I: BSONWriter, D: BSONDocumentReader](id: I): Fu[Option[D]] =
one[D]($id(id))

def byId[D: BSONDocumentReader](id: String): Fu[Option[D]] = one[D]($id(id))
def byId[D](using BSONDocumentReader[D]): [I] => I => BSONWriter[I] ?=> Fu[Option[D]] =
[I] => (id: I) => one[D]($id(id))

def byId[D: BSONDocumentReader](id: String, projection: Bdoc): Fu[Option[D]] = one[D]($id(id), projection)
def byTypedId[I: BSONWriter, D: BSONDocumentReader](id: I, projection: Bdoc): Fu[Option[D]] =
one[D]($id(id), projection)

def byIdProj[D](using BSONDocumentReader[D]): [I] => (I, Bdoc) => BSONWriter[I] ?=> Fu[Option[D]] =
[I] => (id: I, projection: Bdoc) => byTypedId[I, D](id, projection)

def byIds[D: BSONDocumentReader, I: BSONWriter](
ids: Iterable[I],
Expand Down
6 changes: 3 additions & 3 deletions modules/game/src/main/Captcher.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ final private class Captcher(gameRepo: GameRepo)(using ec: scala.concurrent.Exec

case AnyCaptcha => sender() ! Impl.current

case GetCaptcha(id) => Impl.get(GameId(id)).pipeTo(sender()).unit
case GetCaptcha(id) => Impl.get(id).pipeTo(sender()).unit

case actorApi.NewCaptcha => Impl.refresh.unit

case ValidCaptcha(id, solution) => Impl.get(GameId(id)).map(_ valid solution).pipeTo(sender()).unit
case ValidCaptcha(id, solution) => Impl.get(id).map(_ valid solution).pipeTo(sender()).unit

private object Impl:

Expand All @@ -47,7 +47,7 @@ final private class Captcher(gameRepo: GameRepo)(using ec: scala.concurrent.Exec
if (find(c.gameId).isEmpty)
challenges = NonEmptyList(c, challenges.toList take capacity)

private def find(id: String): Option[Captcha] =
private def find(id: GameId): Option[Captcha] =
challenges.find(_.gameId == id)

private def createFromDb: Fu[Option[Captcha]] =
Expand Down
6 changes: 3 additions & 3 deletions modules/game/src/main/Game.scala
Original file line number Diff line number Diff line change
Expand Up @@ -634,7 +634,7 @@ object Game:
case class StartedAt(startColor: Color, startedAtTurn: Int):
def pov(color: Color) = SideAndStart(color, startColor, startedAtTurn)

val syntheticId = "synthetic"
val syntheticId = GameId("synthetic")

val maxPlaying = 200 // including correspondence
val maxPlayingRealtime = 100
Expand Down Expand Up @@ -704,8 +704,8 @@ object Game:
def takeGameId(fullId: String) = GameId(fullId take gameIdSize)
def takePlayerId(fullId: String) = GamePlayerId(fullId drop gameIdSize)

val idRegex = """[\w-]{8}""".r
def validId(id: GameId) = idRegex matches id
private val idRegex = """[\w-]{8}""".r
def validId(id: GameId) = idRegex matches id.value

def isBoardCompatible(game: Game): Boolean =
game.clock.fold(true) { c =>
Expand Down
4 changes: 2 additions & 2 deletions modules/game/src/main/GameRepo.scala
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ final class GameRepo(val coll: Coll)(using ec: scala.concurrent.ExecutionContext
val fixedColorLobbyCache = lila.memo.ExpireSetMemo[GameId](2 hours)

def game(gameId: GameId): Fu[Option[Game]] = coll.byId[Game](gameId)
def gameFromSecondary(gameId: GameId): Fu[Option[Game]] = coll.secondaryPreferred.byId[Game](gameId)
def gameFromSecondary(gameId: GameId): Fu[Option[Game]] = coll.secondaryPreferred.byId[Game, GameId](gameId)

def gamesFromSecondary(gameIds: Seq[GameId]): Fu[List[Game]] =
coll.byOrderedIds[Game, GameId](gameIds, readPreference = ReadPreference.secondaryPreferred)(_.id)
Expand All @@ -34,7 +34,7 @@ final class GameRepo(val coll: Coll)(using ec: scala.concurrent.ExecutionContext

object light:

def game(gameId: GameId): Fu[Option[LightGame]] = coll.byId[LightGame](gameId, LightGame.projection)
def game(gameId: GameId): Fu[Option[LightGame]] = coll.byId[LightGame](gameId.value, LightGame.projection)

def pov(gameId: GameId, color: Color): Fu[Option[LightPov]] =
game(gameId) dmap2 { (game: LightGame) =>
Expand Down
2 changes: 1 addition & 1 deletion modules/game/src/main/PgnDump.scala
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ final class PgnDump(
Pgn(ts, turns)
}

private def gameUrl(id: String) = s"$baseUrl/$id"
private def gameUrl(id: GameId) = s"$baseUrl/$id"

private def gameLightUsers(game: Game): Fu[(Option[LightUser], Option[LightUser])] =
(game.whitePlayer.userId ?? lightUserApi.async) zip (game.blackPlayer.userId ?? lightUserApi.async)
Expand Down
14 changes: 7 additions & 7 deletions modules/memo/src/main/ExpireSetMemo.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,29 @@ import alleycats.Zero
import scala.annotation.nowarn
import scala.concurrent.duration.FiniteDuration

final class ExpireSetMemo[K <: String](ttl: FiniteDuration):
final class ExpireSetMemo[K](ttl: FiniteDuration)(using ev: K =:= String):

private val cache: Cache[K, Boolean] = CacheApi.scaffeineNoScheduler
private val cache: Cache[String, Boolean] = CacheApi.scaffeineNoScheduler
.expireAfterWrite(ttl)
.build[K, Boolean]()
.build[String, Boolean]()

@nowarn def get(key: K): Boolean = cache.underlying.getIfPresent(key) == true

def intersect(keys: Iterable[K]): Set[K] =
keys.nonEmpty ?? {
val res = cache getAllPresent keys
val res = cache getAllPresent keys.map(ev.apply)
keys filter res.contains toSet
}

def put(key: K) = cache.put(key, true)

def putAll(keys: Iterable[K]) = cache putAll keys.view.map(_ -> true).to(Map)
def putAll(keys: Iterable[K]) = cache putAll keys.view.map(k => ev(k) -> true).toMap

def remove(key: K) = cache invalidate key

def removeAll(keys: Iterable[K]) = cache invalidateAll keys
def removeAll(keys: Iterable[K]) = cache invalidateAll keys.map(ev.apply)

def keys: Iterable[K] = cache.asMap().keys
def keys: Iterable[K] = cache.asMap().keys map ev.flip.apply

def keySet: Set[K] = keys.toSet

Expand Down
8 changes: 4 additions & 4 deletions modules/memo/src/main/OnceEvery.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import scala.concurrent.duration.FiniteDuration

object OnceEvery:

def apply[K <: String](ttl: FiniteDuration): K => Boolean =
def apply[K](ttl: FiniteDuration)(using bts: BasicallyTheSame[K, String]): K => Boolean =

val cache = new ExpireSetMemo[K](ttl)
val cache = new ExpireSetMemo[String](ttl)

key => {
val isNew = !cache.get(key)
if (isNew) cache.put(key)
val isNew = !cache.get(bts(key))
if (isNew) cache.put(bts(key))
isNew
}

Expand Down
2 changes: 1 addition & 1 deletion modules/security/src/main/GarbageCollector.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ final class GarbageCollector(

private val logger = lila.security.logger.branch("GarbageCollector")

private val justOnce = lila.memo.OnceEvery(10 minutes)
private val justOnce = lila.memo.OnceEvery[User.ID](10 minutes)

private case class ApplyData(user: User, ip: IpAddress, email: EmailAddress, req: RequestHeader):
override def toString = s"${user.username} $ip ${email.value} $req"
Expand Down
4 changes: 2 additions & 2 deletions modules/security/src/main/UserLogins.scala
Original file line number Diff line number Diff line change
Expand Up @@ -151,8 +151,8 @@ final class UserLoginsApi(
for {
doc <- docs
user <- doc.getAsOpt[User]("user")
ips <- doc.getAsOpt[Set[IpAddress]]("ips")
fps <- doc.getAsOpt[Set[FingerHash]]("fps")
ips <- doc.getAsOpt[Set[IpAddress]]("ips")(collectionReader)
fps <- doc.getAsOpt[Set[FingerHash]]("fps")(collectionReader)
} yield OtherUser(user, ips intersect ipSet, fps intersect fpSet)
}

Expand Down
4 changes: 2 additions & 2 deletions modules/user/src/main/JsonView.scala
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ object JsonView:
})

def notes(ns: List[Note])(implicit lightUser: LightUserApi) =
lightUser.preloadMany(ns.flatMap(_.userIds).distinct) inject Json.arr {
lightUser.preloadMany(ns.flatMap(_.userIds).distinct) inject JsArray(
ns.map { note =>
Json
.obj(
Expand All @@ -147,7 +147,7 @@ object JsonView:
.add("mod", note.mod)
.add("dox", note.dox)
}
}
)

given leaderboardsWrites(using OWrites[User.LightPerf]): OWrites[Perfs.Leaderboards] =
OWrites { leaderboards =>
Expand Down
Loading

0 comments on commit efce0dc

Please sign in to comment.