Skip to content

Commit

Permalink
Make SearchFilters pagination agnostic
Browse files Browse the repository at this point in the history
  • Loading branch information
pomadchin committed Mar 30, 2022
1 parent d6af40a commit 670e58d
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 72 deletions.
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
package com.azavea.stac4s.api.client

import com.azavea.stac4s.api.client.SttpStacClientF.PaginationToken
import com.azavea.stac4s.api.client.util.ClientCodecs
import com.azavea.stac4s.geometry.Geometry
import com.azavea.stac4s.{Bbox, TemporalExtent}
import com.azavea.stac4s.{Bbox, TemporalExtent, productFieldNames}

import cats.syntax.option._
import eu.timepit.refined.types.numeric.NonNegInt
import io.circe._
import io.circe.refined._
import monocle.Lens
import monocle.macros.GenLens
import io.circe.syntax._

case class SearchFilters(
bbox: Option[Bbox] = None,
Expand All @@ -20,11 +18,11 @@ case class SearchFilters(
items: List[String] = Nil,
limit: Option[NonNegInt] = None,
query: Map[String, List[Query]] = Map.empty,
next: Option[PaginationToken] = None
paginationBody: JsonObject = JsonObject.empty
)

object SearchFilters extends ClientCodecs {
implicit val paginationTokenLens: Lens[SearchFilters, Option[PaginationToken]] = GenLens[SearchFilters](_.next)
val searchFilterFields = productFieldNames[SearchFilters]

implicit val searchFiltersDecoder: Decoder[SearchFilters] = { c =>
for {
Expand All @@ -35,7 +33,7 @@ object SearchFilters extends ClientCodecs {
itemsOption <- c.downField("ids").as[Option[List[String]]]
limit <- c.downField("limit").as[Option[NonNegInt]]
query <- c.get[Option[Map[String, List[Query]]]]("query")
paginationToken <- c.get[Option[PaginationToken]]("next")
document <- c.value.as[JsonObject]
} yield {
SearchFilters(
bbox,
Expand All @@ -45,30 +43,32 @@ object SearchFilters extends ClientCodecs {
itemsOption getOrElse Nil,
limit,
query getOrElse Map.empty,
paginationToken
document.filter { case (k, _) => !searchFilterFields.contains(k) }
)
}
}

implicit val searchFiltersEncoder: Encoder[SearchFilters] = Encoder.forProduct8(
"bbox",
"datetime",
"intersects",
"collections",
"ids",
"limit",
"query",
"next"
)(filters =>
(
filters.bbox,
filters.datetime,
filters.intersects,
filters.collections.some.filter(_.nonEmpty),
filters.items.some.filter(_.nonEmpty),
filters.limit,
filters.query.some.filter(_.nonEmpty),
filters.next
)
)
implicit val searchFiltersEncoder: Encoder[SearchFilters] = { filters =>
val fieldsEncoder = Encoder.forProduct7(
"bbox",
"datetime",
"intersects",
"collections",
"ids",
"limit",
"query"
) { filters: SearchFilters =>
(
filters.bbox,
filters.datetime,
filters.intersects,
filters.collections.some.filter(_.nonEmpty),
filters.items.some.filter(_.nonEmpty),
filters.limit,
filters.query.some.filter(_.nonEmpty)
)
}

fieldsEncoder(filters).deepMerge(filters.paginationBody.asJson)
}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
package com.azavea.stac4s.api.client

import com.azavea.stac4s.api.client.SttpStacClientF.PaginationToken
import com.azavea.stac4s.api.client.util.ClientCodecs
import com.azavea.stac4s.{Bbox, TemporalExtent}
import com.azavea.stac4s.{Bbox, TemporalExtent, productFieldNames}

import cats.syntax.option._
import eu.timepit.refined.types.numeric.NonNegInt
import geotrellis.vector.{io => _, _}
import io.circe._
import io.circe.refined._
import monocle.Lens
import monocle.macros.GenLens
import io.circe.syntax._

case class SearchFilters(
bbox: Option[Bbox] = None,
Expand All @@ -20,11 +18,11 @@ case class SearchFilters(
items: List[String] = Nil,
limit: Option[NonNegInt] = None,
query: Map[String, List[Query]] = Map.empty,
next: Option[PaginationToken] = None
paginationBody: JsonObject = JsonObject.empty
)

object SearchFilters extends ClientCodecs {
implicit val paginationTokenLens: Lens[SearchFilters, Option[PaginationToken]] = GenLens[SearchFilters](_.next)
val searchFilterFields = productFieldNames[SearchFilters]

implicit val searchFiltersDecoder: Decoder[SearchFilters] = { c =>
for {
Expand All @@ -35,7 +33,7 @@ object SearchFilters extends ClientCodecs {
itemsOption <- c.downField("ids").as[Option[List[String]]]
limit <- c.downField("limit").as[Option[NonNegInt]]
query <- c.get[Option[Map[String, List[Query]]]]("query")
paginationToken <- c.get[Option[PaginationToken]]("next")
document <- c.value.as[JsonObject]
} yield {
SearchFilters(
bbox,
Expand All @@ -45,30 +43,32 @@ object SearchFilters extends ClientCodecs {
itemsOption getOrElse Nil,
limit,
query getOrElse Map.empty,
paginationToken
document.filter { case (k, _) => !searchFilterFields.contains(k) }
)
}
}

implicit val searchFiltersEncoder: Encoder[SearchFilters] = Encoder.forProduct8(
"bbox",
"datetime",
"intersects",
"collections",
"ids",
"limit",
"query",
"next"
)(filters =>
(
filters.bbox,
filters.datetime,
filters.intersects,
filters.collections.some.filter(_.nonEmpty),
filters.items.some.filter(_.nonEmpty),
filters.limit,
filters.query.some.filter(_.nonEmpty),
filters.next
)
)
implicit val searchFiltersEncoder: Encoder[SearchFilters] = { filters =>
val fieldsEncoder = Encoder.forProduct7(
"bbox",
"datetime",
"intersects",
"collections",
"ids",
"limit",
"query"
) { filters: SearchFilters =>
(
filters.bbox,
filters.datetime,
filters.intersects,
filters.collections.some.filter(_.nonEmpty),
filters.items.some.filter(_.nonEmpty),
filters.limit,
filters.query.some.filter(_.nonEmpty)
)
}

fieldsEncoder(filters).deepMerge(filters.paginationBody.asJson)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import eu.timepit.refined.types.string.NonEmptyString
import fs2.Stream
import io.circe.syntax._
import io.circe.{Encoder, Json, JsonObject}
import monocle.Lens
import sttp.client3.circe.asJson
import sttp.client3.{Response, SttpBackend, UriContext, basicRequest}
import sttp.model.{MediaType, Uri}
Expand All @@ -31,16 +30,16 @@ case class SttpStacClientF[F[_]: MonadThrow, S: Encoder](

def search(filter: Option[S]): Stream[F, StacItem] = {
val emptyJson = JsonObject.empty.asJson
// the initial filter may contain the paginationToken that is used for the initial query
val initialBody = filter.map(_.asJson).getOrElse(emptyJson)
// the initial filter may contain the paginationBody that is used for the initial query
val initialBody = filter.map(_.asJson.deepDropNullValues).getOrElse(emptyJson)
Stream
.unfoldLoopEval((baseUri.addPath("search"), initialBody)) { case (link, request) =>
client
.send(
basicRequest
.post(link)
.contentType(MediaType.ApplicationJson)
.body(request.deepDropNullValues.noSpaces)
.body(request.noSpaces)
.response(asJson[Json])
)
.flatMap { response =>
Expand Down Expand Up @@ -147,8 +146,6 @@ case class SttpStacClientF[F[_]: MonadThrow, S: Encoder](
}

object SttpStacClientF {
// TODO: should be a newtype
type PaginationToken = NonEmptyString

implicit class ResponseEitherJsonOps[E <: Exception](val self: Response[Either[E, Json]]) extends AnyVal {

Expand Down Expand Up @@ -196,14 +193,6 @@ object SttpStacClientF {
self.body.flatMap(_.hcursor.downField("collections").as[List[StacCollection]]).liftTo[F]
}

implicit class StacFilterOps[S](val self: Option[S]) extends AnyVal {

def setPaginationToken(
token: Option[PaginationToken]
)(implicit l: Lens[S, Option[PaginationToken]], enc: Encoder[S]): Json =
self.map(l.set(token)(_).asJson).getOrElse(JsonObject.empty.asJson)
}

implicit class JsonOps[S](val self: Json) extends AnyVal {

def setPaginationBody(body: Option[Json]): Json = {
Expand Down

0 comments on commit 670e58d

Please sign in to comment.