Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

STAC Client pagination support #327

Merged
merged 5 commits into from
May 24, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [Unreleased]
### Added
- Add Scala 2.13 cross compilation [#310](https://github.com/azavea/stac4s/pull/310)
- STAC Client pagination support [#327](https://github.com/azavea/stac4s/pull/327)

### Changed
- Make StacClient type alias a trait [#325](https://github.com/azavea/stac4s/pull/325)
Expand Down
32 changes: 12 additions & 20 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import xerial.sbt.Sonatype._
import Dependencies._

lazy val commonSettings = Seq(
// We are overriding the default behavior of sbt-git which, by default, only
Expand Down Expand Up @@ -104,22 +103,20 @@ lazy val credentialSettings = Seq(

val jvmGeometryDependencies = Def.setting {
Seq(
"org.locationtech.jts" % "jts-core" % Versions.Jts,
geotrellis("vector").value
"org.locationtech.jts" % "jts-core" % Versions.Jts,
"org.locationtech.geotrellis" %% "geotrellis-vector" % Versions.GeoTrellis.value
)
}

val coreDependenciesJVM = Def.setting {
Seq(
"org.threeten" % "threeten-extra" % Versions.ThreeTenExtra
) ++ jvmGeometryDependencies.value
Seq("org.threeten" % "threeten-extra" % Versions.ThreeTenExtra) ++ jvmGeometryDependencies.value
}

val testingDependenciesJVM = Def.setting {
Seq(
geotrellis("vector").value,
"org.locationtech.jts" % "jts-core" % Versions.Jts,
"org.threeten" % "threeten-extra" % Versions.ThreeTenExtra
"org.locationtech.geotrellis" %% "geotrellis-vector" % Versions.GeoTrellis.value,
"org.locationtech.jts" % "jts-core" % Versions.Jts,
"org.threeten" % "threeten-extra" % Versions.ThreeTenExtra
)
}

Expand All @@ -131,7 +128,7 @@ val testRunnerDependenciesJVM = Seq(

lazy val root = project
.in(file("."))
.settings(moduleName := "root")
.settings(name := "stac4s")
.settings(commonSettings)
.settings(publishSettings)
.settings(noPublishSettings)
Expand Down Expand Up @@ -182,11 +179,7 @@ lazy val testing = crossProject(JSPlatform, JVMPlatform)
)
)
.jvmSettings(libraryDependencies ++= testingDependenciesJVM.value)
.jsSettings(
libraryDependencies ++= Seq(
"io.github.cquiroz" %%% "scala-java-time" % "2.3.0" % Test
)
)
.jsSettings(libraryDependencies += "io.github.cquiroz" %%% "scala-java-time" % "2.3.0" % Test)

lazy val testingJVM = testing.jvm
lazy val testingJS = testing.js
Expand All @@ -203,11 +196,7 @@ lazy val coreTest = crossProject(JSPlatform, JVMPlatform)
"org.scalatestplus" %%% "scalacheck-1-14" % Versions.ScalatestPlusScalacheck % Test
)
)
.jsSettings(
libraryDependencies ++= Seq(
"io.github.cquiroz" %%% "scala-java-time" % "2.3.0" % Test
)
)
.jsSettings(libraryDependencies += "io.github.cquiroz" %%% "scala-java-time" % "2.3.0" % Test)

lazy val coreTestJVM = coreTest.jvm
lazy val coreTestJS = coreTest.js
Expand All @@ -220,6 +209,8 @@ lazy val client = crossProject(JSPlatform, JVMPlatform)
.settings(publishSettings)
.settings(
libraryDependencies ++= Seq(
"com.github.julien-truffaut" %%% "monocle-core" % Versions.Monocle,
"com.github.julien-truffaut" %%% "monocle-macro" % Versions.Monocle,
"io.circe" %%% "circe-core" % Versions.Circe,
"io.circe" %%% "circe-generic" % Versions.Circe,
"io.circe" %%% "circe-refined" % Versions.Circe,
Expand All @@ -232,6 +223,7 @@ lazy val client = crossProject(JSPlatform, JVMPlatform)
"com.softwaremill.sttp.client3" %%% "json-common" % Versions.Sttp,
"com.softwaremill.sttp.model" %%% "core" % Versions.SttpModel,
"com.softwaremill.sttp.shared" %%% "core" % Versions.SttpShared,
"co.fs2" %%% "fs2-core" % Versions.Fs2,
"org.scalatest" %%% "scalatest" % Versions.Scalatest % Test
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import com.azavea.stac4s.jsTypes.TemporalExtent
import eu.timepit.refined.types.numeric.NonNegInt
import io.circe._
import io.circe.refined._
import monocle.Lens
import monocle.macros.GenLens

case class SearchFilters(
bbox: Option[Bbox] = None,
Expand All @@ -21,6 +23,7 @@ case class SearchFilters(
)

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

implicit val searchFiltersDecoder: Decoder[SearchFilters] = { c =>
for {
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package com.azavea.stac4s.api.client

import cats.MonadError
import cats.MonadThrow
import sttp.client3.SttpBackend
import sttp.model.Uri

object SttpStacClient {

def apply[F[_]: MonadError[*[_], Throwable]](client: SttpBackend[F, Any], baseUri: Uri): SttpStacClient[F] =
def apply[F[_]: MonadThrow](client: SttpBackend[F, Any], baseUri: Uri): SttpStacClient[F] =
SttpStacClientF[F, SearchFilters](client, baseUri)
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package com.azavea.stac4s.api

package object client {
type SttpStacClient[F[_]] = SttpStacClientF[F, SearchFilters]
type SttpStacClient[F[_]] = SttpStacClientF[F, SearchFilters]
type StacClient[F[_]] = StacClientF[F, SearchFilters]
type StreamingStacClientFS2[F[_]] = StreamingStacClientF[F, fs2.Stream[F, *], SearchFilters]
type StreamingStacClient[F[_], G[_]] = StreamingStacClientF[F, G, SearchFilters]
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ 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

case class SearchFilters(
bbox: Option[Bbox] = None,
Expand All @@ -21,6 +23,7 @@ case class SearchFilters(
)

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

implicit val searchFiltersDecoder: Decoder[SearchFilters] = { c =>
for {
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package com.azavea.stac4s.api.client

import cats.MonadError
import cats.MonadThrow
import sttp.client3.SttpBackend
import sttp.model.Uri

object SttpStacClient {

def apply[F[_]: MonadError[*[_], Throwable]](client: SttpBackend[F, Any], baseUri: Uri): SttpStacClient[F] =
def apply[F[_]: MonadThrow](client: SttpBackend[F, Any], baseUri: Uri): SttpStacClient[F] =
SttpStacClientF[F, SearchFilters](client, baseUri)
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.azavea.stac4s.api

package object client {
type SttpStacClient[F[_]] = SttpStacClientF[F, SearchFilters]
type StacClient[F[_]] = StacClientF[F, SearchFilters]
type StreamingStacClientFS2[F[_]] = StreamingStacClientF[F, fs2.Stream[F, *], SearchFilters]
type StreamingStacClient[F[_], G[_]] = StreamingStacClientF[F, G, SearchFilters]
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@ package com.azavea.stac4s.api.client

import cats.syntax.either._
import eu.timepit.refined.types.numeric.PosInt
import io.circe
import io.circe.generic.semiauto._
import io.circe.parser.parse
import io.circe.refined._
import io.circe.syntax._
import io.circe.{Decoder, Encoder}
import io.circe.{Decoder, Encoder, Error}

import java.time.Instant
import java.util.Base64
Expand All @@ -18,26 +17,26 @@ final case class PaginationToken(timestampAtLeast: Instant, serialIdGreaterThan:
* https://github.com/azavea/franklin/blob/f5be8ddf48661c5bc43cbd22cb7277e961641803/application/src/main/scala/com/azavea/franklin/api/schemas/package.scala#L84-L85
*/
object PaginationToken {
val b64Encoder = Base64.getEncoder
val b64Decoder = Base64.getDecoder

val defaultDecoder: Decoder[PaginationToken] = deriveDecoder
val defaultEncoder: Encoder[PaginationToken] = deriveEncoder

val b64Encoder: Base64.Encoder = Base64.getEncoder
val b64Decoder: Base64.Decoder = Base64.getDecoder

def encPaginationToken(token: PaginationToken): String = b64Encoder.encodeToString(
token.asJson(defaultEncoder).noSpaces.getBytes
)

def decPaginationToken(encoded: String): Either[circe.Error, PaginationToken] = {
def decPaginationTokenEither(encoded: String): Either[Error, PaginationToken] = {
val jsonString = new String(b64Decoder.decode(encoded))
for {
js <- parse(jsonString)
decoded <- js.as[PaginationToken](defaultDecoder)
} yield decoded
}

implicit val dec: Decoder[PaginationToken] =
Decoder.decodeString.emap(str => decPaginationToken(str).leftMap(_.getMessage))
implicit val paginationTokenDecoder: Decoder[PaginationToken] =
Decoder.decodeString.emap(str => decPaginationTokenEither(str).leftMap(_.getMessage))

implicit val enc: Encoder[PaginationToken] = { encPaginationToken(_).asJson }
implicit val paginationTokenEncoder: Encoder[PaginationToken] = { encPaginationToken(_).asJson }
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@ import com.azavea.stac4s._
import eu.timepit.refined.types.string.NonEmptyString

trait StacClientF[F[_], S] {
def search: F[List[StacItem]]
def search(filter: S): F[List[StacItem]]
def collections: F[List[StacCollection]]
def collection(collectionId: NonEmptyString): F[StacCollection]
def items(collectionId: NonEmptyString): F[List[StacItem]]
def item(collectionId: NonEmptyString, itemId: NonEmptyString): F[StacItem]
def itemCreate(collectionId: NonEmptyString, item: StacItem): F[StacItem]
def collectionCreate(collection: StacCollection): F[StacCollection]
}

trait StreamingStacClientF[F[_], G[_], S] extends StacClientF[F, S] {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

G here is a separate parameter to make it independent from F.

def search: G[StacItem]
def search(filter: S): G[StacItem]
def collections: G[StacCollection]
def items(collectionId: NonEmptyString): G[StacItem]
}
Loading