Skip to content

Instantly share code, notes, and snippets.

@teigen
Created December 11, 2016 22:59
Show Gist options
  • Save teigen/6d0951c72702a1d7d81164e5fcc51047 to your computer and use it in GitHub Desktop.
Save teigen/6d0951c72702a1d7d81164e5fcc51047 to your computer and use it in GitHub Desktop.
influx.db data types and decoders for json query results
package influxdb
import argonaut.Argonaut._
import shapeless._
object Main {
def main(args: Array[String]): Unit = {
val one =
"""
|{
| "results": [
| {
| "series": [
| {
| "name": "cpu_load_short",
| "columns": [
| "time",
| "value"
| ],
| "values": [
| [
| "2015-01-29T21:55:43.702900257Z",
| 0.55
| ],
| [
| "2015-01-29T21:55:43.702900257Z",
| 23422
| ],
| [
| "2015-06-11T20:46:02Z",
| 0.64
| ]
| ]
| }
| ]
| }
| ]
|}
""".stripMargin.decodeEither[Results].toEither.right.get
val two =
"""
|{
| "results": [
| {
| "series": [
| {
| "name": "cpu_load_short",
| "columns": [
| "time",
| "value"
| ],
| "values": [
| [
| "2015-01-29T21:55:43.702900257Z",
| 0.55
| ],
| [
| "2015-01-29T21:55:43.702900257Z",
| 23422
| ],
| [
| "2015-06-11T20:46:02Z",
| 0.64
| ]
| ]
| }
| ]
| },
| {
| "series": [
| {
| "name": "cpu_load_count",
| "columns": [
| "time",
| "count"
| ],
| "values": [
| [
| "1970-01-01T00:00:00Z",
| 3
| ]
| ]
| }
| ]
| }
| ]
|}
""".stripMargin.decodeEither[Results].toEither.right.get
val three =
"""
|{
| "results": [
| {
| "series": [
| {
| "name": "cpu_load_short",
| "columns": [
| "time",
| "value"
| ],
| "values": [
| [
| "2015-01-29T21:55:43.702900257Z",
| 0.55
| ],
| [
| "2015-01-29T21:55:43.702900257Z",
| 23422
| ],
| [
| "2015-06-11T20:46:02Z",
| 0.64
| ]
| ]
| },
| {
| "name": "cpu_load_count",
| "columns": [
| "time",
| "count"
| ],
| "values": [
| [
| "1970-01-01T00:00:00Z",
| 3
| ]
| ]
| }
| ]
| },
| {
| "series": [
| {
| "name": "cpu_load_count",
| "columns": [
| "time",
| "count"
| ],
| "values": [
| [
| "1970-01-01T00:00:00Z",
| 3
| ]
| ]
| }
| ]
| }
| ]
|}
""".stripMargin.decodeEither[Results].toEither.right.get
case class CpuLoadShort(time:String, value:Double)
case class CpuLoadCount(time:String, count:Int)
val oneAs:Either[String, List[CpuLoadShort]] =
one.as[CpuLoadShort]
val twoAs:Either[String, List[CpuLoadShort] :: List[CpuLoadCount] :: HNil] =
two.as[CpuLoadShort :: CpuLoadCount :: HNil]
val threeAs:Either[String, (List[CpuLoadShort] :: List[CpuLoadCount] :: HNil) :: List[CpuLoadCount] :: HNil] =
three.as[(CpuLoadShort :: CpuLoadCount :: HNil) :: CpuLoadCount :: HNil]
println(oneAs)
println(twoAs)
println(threeAs)
}
}
package influxdb
import argonaut._, Argonaut._
import shapeless._
object Result {
object Decoder {
type Aux[A, B] = Decoder[A]{ type Out = B }
import HList.ListCompat._
implicit def SingleSerieDecoder[A](implicit d:Serie.Decoder[A]):Decoder.Aux[A, List[A]] =
new Decoder[A]{
type Out = List[A]
override def decode(series: List[Serie]): Either[String, List[A]] =
series match {
case Serie(_, columns, values) :: Nil => d.decode(columns, values)
case xs => Left("expected a single series, but got " + xs)
}
}
implicit val HNilDecoder:Decoder.Aux[HNil, HNil] =
new Decoder[HNil]{
type Out = HNil
override def decode(series: List[Serie]): Either[String, HNil] =
if(series.isEmpty) Right(HNil) else Left("expected Nil, but got " + series)
}
implicit def HConsDecoder[H, T <: HList, TOut <: HList](implicit dh:Serie.Decoder[H],
dt:Decoder.Aux[T, TOut]):Decoder.Aux[H :: T, List[H] :: TOut] =
new Decoder[H :: T] {
type Out = List[H] :: TOut
override def decode(series: List[Serie]): Either[String, ::[List[H], TOut]] =
series match {
case Serie(_, columns, values) :: xs =>
for {
h <- dh.decode(columns, values).right
t <- dt.decode(xs).right
} yield h :: t
case Nil => Left("expected at least one series, but got Nil")
}
}
}
trait Decoder[A]{
type Out
def decode(series:List[Serie]):Either[String, Out]
}
implicit val codec = CodecJson.derive[Result]
}
case class Result(series:List[Serie]){
def as[A](implicit d:Result.Decoder[A]):Either[String, d.Out] =
d.decode(series)
}
package influxdb
import argonaut._, Argonaut._
import shapeless._
object Results {
object Decoder {
type Aux[A, B] = Decoder[A]{ type Out = B }
import HList.ListCompat._
implicit def SingleResultDecoder[A, B](implicit d:Result.Decoder.Aux[A, B]):Decoder.Aux[A, B] =
new Decoder[A]{
type Out = B
override def decode(results: List[Result]): Either[String, B] =
results match {
case Result(series) :: Nil => d.decode(series)
case xs => Left("expected a single Result, but got " + xs)
}
}
implicit val HNilDecoder:Decoder.Aux[HNil, HNil] =
new Decoder[HNil]{
type Out = HNil
override def decode(results: List[Result]): Either[String, HNil] =
if(results.isEmpty) Right(HNil) else Left("expected Nil, but got " + results)
}
implicit def HConsDecoder[H, T <: HList, TOut <: HList](implicit dh:Result.Decoder[H],
dt:Decoder.Aux[T, TOut]):Decoder.Aux[H :: T, dh.Out :: TOut] =
new Decoder[H :: T] {
type Out = dh.Out :: TOut
override def decode(results: List[Result]): Either[String, dh.Out :: TOut] =
results match {
case Result(series) :: xs =>
for {
h <- dh.decode(series).right
t <- dt.decode(xs).right
} yield h :: t
case Nil => Left("expected at least one Result, but got Nil")
}
}
}
trait Decoder[A]{
type Out
def decode(results:List[Result]):Either[String, Out]
}
implicit val codec = CodecJson.derive[Results]
}
case class Results(results:List[Result]){
def as[A](implicit d:Results.Decoder[A]):Either[String, d.Out] =
d.decode(results)
}
package influxdb
import argonaut._, Argonaut._
import shapeless._, labelled._
object Serie {
object IndexedDecoder {
def index[A : DecodeJson](i:Int):IndexedDecoder[A] =
IndexedDecoder(_.apply(i).as[A].toEither.left.map(_._1))
}
case class IndexedDecoder[A](decode:List[Json] => Either[String, A]){
def map[B](f:A => B):IndexedDecoder[B] =
IndexedDecoder(decode(_).right.map(f))
def merge[B, C](db:IndexedDecoder[B])(f:(A, B) => C):IndexedDecoder[C] =
IndexedDecoder{ row =>
for {
a <- decode(row).right
b <- db.decode(row).right
} yield f(a, b)
}
}
object Decoder {
def column[A : DecodeJson](name:String):Decoder[A] =
Decoder(columns =>
columns
.get(name)
.map(IndexedDecoder.index[A])
.toRight("could not find column '"+name + "' in " + columns.keySet.mkString("[", ", ", "]")))
implicit def HNilDecoder:Decoder[HNil] =
Decoder(_ => Right(IndexedDecoder(_ => Right(HNil))))
implicit def HConsDecoder[K <: Symbol, H, T <: HList](implicit key:Witness.Aux[K],
hd:DecodeJson[H],
td:Decoder[T]):Decoder[FieldType[K, H] :: T] =
(column[H](key.value.name).map(h => field[K](h)) merge td)(_ :: _)
implicit def LabelledGenericDecoder[A, B](implicit gen:LabelledGeneric.Aux[A, B], db:Decoder[B]):Decoder[A] =
db.map(gen.from)
}
case class Decoder[A](indexed:Map[String, Int] => Either[String, IndexedDecoder[A]]) {
def decode(columns:List[String], rows:List[List[Json]]):Either[String, List[A]] = {
indexed(columns.zipWithIndex.toMap).right.flatMap{
d => rows.foldRight[Either[String, List[A]]](Right(Nil))((v, acc) =>
for {
values <- acc.right
a <- d.decode(v).right
} yield a :: values
)
}
}
def map[B](f:A => B):Decoder[B] =
Decoder(indexed(_).right.map(_.map(f)))
def merge[B, C](sb:Decoder[B])(f:(A, B) => C):Decoder[C] =
Decoder{ columns =>
for {
da <- indexed(columns).right
db <- sb.indexed(columns).right
} yield (da merge db) (f)
}
}
implicit val codec = CodecJson.derive[Serie]
}
case class Serie(name:String, columns:List[String], values:List[List[Json]]){
def as[A](implicit ds:Serie.Decoder[A]):Either[String, List[A]] =
ds.decode(columns, values)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment