Skip to content

Instantly share code, notes, and snippets.

@Swoorup
Created November 19, 2021 10:00
Show Gist options
  • Save Swoorup/8e884972b30353320e7020e0caf3e33b to your computer and use it in GitHub Desktop.
Save Swoorup/8e884972b30353320e7020e0caf3e33b to your computer and use it in GitHub Desktop.
Range Codec for skunk
package infrastructure.repository.codec
import skunk.Codec
import PgRangeSupportUtils.*
enum EdgeType:
case `[_,_)`
case `(_,_]`
case `(_,_)`
case `[_,_]`
case `empty`
case class Range[T](start: Option[T], end: Option[T], edge: EdgeType) {
def as[A](convert: T => A): Range[A] = Range(start.map(convert), end.map(convert), edge)
override def toString: String = edge match {
case EdgeType.`[_,_)` => s"[${oToString(start)},${oToString(end)})"
case EdgeType.`(_,_]` => s"(${oToString(start)},${oToString(end)}]"
case EdgeType.`(_,_)` => s"(${oToString(start)},${oToString(end)})"
case EdgeType.`[_,_]` => s"[${oToString(start)},${oToString(end)}]"
case EdgeType.`empty` => Range.empty_str
}
}
object Range {
def emptyRange[T]: Range[T] = Range[T](None, None, EdgeType.`empty`)
val empty_str = "empty"
def apply[T](start: T, end: T, edge: EdgeType = EdgeType.`[_,_)`): Range[T] = Range(Some(start), Some(end), edge)
}
val int8range: Codec[Range[Long]] = Codec.simple(
_.toString,
safe(mkRangeFn(_.toLong)),
skunk.data.Type.int8range
)
val int4range: Codec[Range[Int]] = Codec.simple(
_.toString,
safe(mkRangeFn(_.toInt)),
skunk.data.Type.int4range
)
object PgRangeSupportUtils {
def safe[A](f: String => A): String => Either[String, A] = s =>
try Right(f(s))
catch { case _: NumberFormatException => Left(s"Invalid: $s")}
// regular expr matchers to range string
val `[_,_)Range` = """\["?([^,"]*)"?,[ ]*"?([^,"]*)"?\)""".r // matches: [_,_)
val `(_,_]Range` = """\("?([^,"]*)"?,[ ]*"?([^,"]*)"?\]""".r // matches: (_,_]
val `(_,_)Range` = """\("?([^,"]*)"?,[ ]*"?([^,"]*)"?\)""".r // matches: (_,_)
val `[_,_]Range` = """\["?([^,"]*)"?,[ ]*"?([^,"]*)"?\]""".r // matches: [_,_]
def mkRangeFn[T](convert: (String => T)): (String => Range[T]) = {
def conv[T](str: String, convert: (String => T)): Option[T] =
Option(str).filterNot(_.isEmpty).map(convert)
(str: String) => str match {
case Range.`empty_str` => Range.emptyRange[T]
case `[_,_)Range`(start, end) => Range(conv(start, convert), conv(end, convert), EdgeType.`[_,_)`)
case `(_,_]Range`(start, end) => Range(conv(start, convert), conv(end, convert), EdgeType.`(_,_]`)
case `(_,_)Range`(start, end) => Range(conv(start, convert), conv(end, convert), EdgeType.`(_,_)`)
case `[_,_]Range`(start, end) => Range(conv(start, convert), conv(end, convert), EdgeType.`[_,_]`)
}
}
def toStringFn[T](toString: (T => String)): (Range[T] => String) =
(r: Range[T]) => r.edge match {
case EdgeType.`empty` => Range.empty_str
case EdgeType.`[_,_)` => s"[${oToString(r.start, toString)},${oToString(r.end, toString)})"
case EdgeType.`(_,_]` => s"(${oToString(r.start, toString)},${oToString(r.end, toString)}]"
case EdgeType.`(_,_)` => s"(${oToString(r.start, toString)},${oToString(r.end, toString)})"
case EdgeType.`[_,_]` => s"[${oToString(r.start, toString)},${oToString(r.end, toString)}]"
}
def mkWithLength[T](start: T, length: Double, edge: EdgeType = EdgeType.`[_,_)`) = {
val upper = (start.asInstanceOf[Double] + length).asInstanceOf[T]
new Range[T](Some(start), Some(upper), edge)
}
////// helper methods
private[infrastructure] def oToString[T](o: Option[T], toString: (T => String) = (r: T) => r.toString) =
o.map(toString).getOrElse("")
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment