Skip to content

Commit

Permalink
Decode facet responses
Browse files Browse the repository at this point in the history
  • Loading branch information
eikek committed Mar 7, 2024
1 parent 08287c1 commit 5cf1d18
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,19 @@
package io.renku.solr.client

import io.bullet.borer.Decoder
import io.bullet.borer.derivation.MapBasedCodecs.deriveDecoder
import io.bullet.borer.NullOptions.given
import io.bullet.borer.derivation.MapBasedCodecs
import io.bullet.borer.derivation.key
import io.renku.solr.client.facet.FacetResponse

final case class QueryResponse[A](
responseHeader: ResponseHeader,
@key("response") responseBody: ResponseBody[A]
@key("response") responseBody: ResponseBody[A],
@key("facets") facetResponse: Option[FacetResponse] = None
):
def map[B](f: A => B): QueryResponse[B] =
copy(responseBody = responseBody.map(f))

object QueryResponse:
given [A](using Decoder[A]): Decoder[QueryResponse[A]] =
deriveDecoder
MapBasedCodecs.deriveDecoder
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* Copyright 2024 Swiss Data Science Center (SDSC)
* A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and
* Eidgenössische Technische Hochschule Zürich (ETHZ).
*
* 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 io.renku.solr.client.facet

import io.renku.solr.client.schema.FieldName
import io.bullet.borer.derivation.key
import io.bullet.borer.Decoder
import io.bullet.borer.derivation.MapBasedCodecs
import io.bullet.borer.Reader

import FacetResponse.Values

final case class FacetResponse(
count: Int,
buckets: Map[FieldName, Values]
):
def isEmpty: Boolean = count == 0 && buckets.isEmpty

private def withBuckets(field: FieldName, values: Values): FacetResponse =
copy(buckets = buckets.updated(field, values))

object FacetResponse:
val empty: FacetResponse = FacetResponse(0, Map.empty)

final case class Bucket(@key("val") value: String, count: Int)
object Bucket:
given Decoder[Bucket] = MapBasedCodecs.deriveDecoder

final case class Values(buckets: Buckets)
object Values:
given Decoder[Values] = MapBasedCodecs.deriveDecoder

type Buckets = Seq[Bucket]

given Decoder[FacetResponse] = new Decoder[FacetResponse] {
def read(r: Reader): FacetResponse =
r.readMapStart()
r.readUntilBreak(empty) { fr =>
val nextKey = r.readString()
if (nextKey == "count") fr.copy(count = r.readInt())
else fr.withBuckets(FieldName(nextKey), r.read[Values]())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ import munit.CatsEffectSuite
import munit.ScalaCheckEffectSuite
import org.scalacheck.effect.PropF
import io.bullet.borer.Reader
import org.scalacheck.Gen
import io.renku.solr.client.facet.{Facet, Facets}

class SolrClientSpec
extends CatsEffectSuite
Expand All @@ -45,30 +47,62 @@ class SolrClientSpec
SchemaCommand.Add(Field(FieldName("roomSeats"), TypeName("roomInt")))
)
withSolrClient().use { client =>
val rooms = Seq(Room("meeting room", "room for meetings", 56))
for {
_ <- truncateAll(client)(
List("roomName", "roomDescription", "roomSeats").map(FieldName.apply),
List("roomText", "roomInt").map(TypeName.apply)
)
_ <- client.modifySchema(cmds)
_ <- client
.insert[Room](Seq(Room("meeting room", "room for meetings", 56)))
.insert[Room](rooms)
r <- client.query[Room](QueryData(QueryString("_type:Room")))
_ <- IO.println(r)
_ = assertEquals(r.responseBody.docs, rooms)
} yield ()
}

test("correct facet queries"):
given Decoder[Unit] = new Decoder {
val decoder: Decoder[Unit] = new Decoder {
def read(r: Reader): Unit =
r.skipElement()
()
}
PropF.forAllF(SolrClientGenerator.facets) { facets =>
val q = QueryData(QueryString("*:*")).withFacet(facets)
withSolrClient().use { client =>
client.query[Unit](q).void
client.query(q)(using decoder).void
}
}

test("decoding facet response"):
val rooms = Gen.listOfN(15, Room.gen).sample.get
val facets =
Facets(Facet.Terms(FieldName("by_name"), FieldName("roomName"), limit = Some(6)))
withSolrClient().use { client =>
for {
_ <- client.delete(QueryString("*:*"))
_ <- client.insert(rooms)
r <- client.query[Room](QueryData(QueryString("*:*")).withFacet(facets))
_ = assert(r.facetResponse.nonEmpty)
_ = assertEquals(r.facetResponse.get.count, 15)
_ = assertEquals(
r.facetResponse.get.buckets(FieldName("by_name")).buckets.size,
6
)
} yield ()
}

object SolrClientSpec:
case class Room(roomName: String, roomDescription: String, roomSeats: Int)
object Room:
val gen: Gen[Room] = for {
name <- Gen
.choose(4, 12)
.flatMap(n => Gen.listOfN(n, Gen.alphaChar))
.map(_.mkString)
descr = s"Room description for $name"
seats <- Gen.choose(15, 350)
} yield Room(name, descr, seats)

given Decoder[Room] = deriveDecoder
given Encoder[Room] = EncoderSupport.deriveWithDiscriminator[Room]

0 comments on commit 5cf1d18

Please sign in to comment.