From d4eea579985d2f9dcfbd329f460d40be90f83990 Mon Sep 17 00:00:00 2001 From: James Santucci Date: Thu, 14 May 2020 13:20:56 -0500 Subject: [PATCH 01/26] Add item extension typeclass and LabelProperties evidence --- .../stac4s/extensions/ItemExtension.scala | 30 +++++++++++++++ .../label/LabelExtensionProperties.scala | 37 +++++++++---------- .../azavea/stac4s/extensions/package.scala | 13 +++++++ 3 files changed, 61 insertions(+), 19 deletions(-) create mode 100644 modules/core/src/main/scala/com/azavea/stac4s/extensions/ItemExtension.scala create mode 100644 modules/core/src/main/scala/com/azavea/stac4s/extensions/package.scala diff --git a/modules/core/src/main/scala/com/azavea/stac4s/extensions/ItemExtension.scala b/modules/core/src/main/scala/com/azavea/stac4s/extensions/ItemExtension.scala new file mode 100644 index 00000000..56ded08b --- /dev/null +++ b/modules/core/src/main/scala/com/azavea/stac4s/extensions/ItemExtension.scala @@ -0,0 +1,30 @@ +package com.azavea.stac4s.extensions + +import com.azavea.stac4s.StacItem + +import io.circe._ +import io.circe.syntax._ + +// typeclass trait for anything that is an extension of item properties +trait ItemExtension[T] { + def getProperties(item: StacItem): ExtensionResult[T] + def extend(item: StacItem, properties: T): StacItem +} + +object ItemExtension { + // summoner + def apply[T](implicit ev: ItemExtension[T]): ItemExtension[T] = ev + + // constructor for anything with a `Decoder` + def instance[T](implicit decoder: Decoder[T], objectEncoder: Encoder.AsObject[T]): ItemExtension[T] = + new ItemExtension[T] { + + def getProperties(item: StacItem): ExtensionResult[T] = + decoder.decodeAccumulating( + item.properties.asJson.hcursor + ) + + def extend(item: StacItem, extensionProperties: T) = + item.copy(properties = item.properties.deepMerge(objectEncoder.encodeObject(extensionProperties))) + } +} diff --git a/modules/core/src/main/scala/com/azavea/stac4s/extensions/label/LabelExtensionProperties.scala b/modules/core/src/main/scala/com/azavea/stac4s/extensions/label/LabelExtensionProperties.scala index 315df1c5..0608dd92 100644 --- a/modules/core/src/main/scala/com/azavea/stac4s/extensions/label/LabelExtensionProperties.scala +++ b/modules/core/src/main/scala/com/azavea/stac4s/extensions/label/LabelExtensionProperties.scala @@ -1,8 +1,11 @@ package com.azavea.stac4s.extensions.label +import com.azavea.stac4s.extensions.ItemExtension + import cats.Eq import cats.implicits._ -import io.circe.{Decoder, Encoder, HCursor} +import io.circe.{Decoder, Encoder, HCursor, Json} +import io.circe.syntax._ case class LabelExtensionProperties( properties: LabelProperties, @@ -16,25 +19,19 @@ case class LabelExtensionProperties( object LabelExtensionProperties { - implicit val encLabelExtensionProperties: Encoder[LabelExtensionProperties] = Encoder.forProduct7( - "label:properties", - "label:classes", - "label:description", - "label:type", - "label:tasks", - "label:methods", - "label:overviews" - )(extensionProps => - ( - extensionProps.properties, - extensionProps.classes, - extensionProps.description, - extensionProps._type, - extensionProps.tasks, - extensionProps.methods, - extensionProps.overviews + implicit val encLabelExtensionPropertiesObject: Encoder.AsObject[LabelExtensionProperties] = Encoder + .AsObject[Map[String, Json]] + .contramapObject((properties: LabelExtensionProperties) => + Map( + "label:properties" -> properties.properties.asJson, + "label:classes" -> properties.classes.asJson, + "label:description" -> properties.description.asJson, + "label:type" -> properties._type.asJson, + "label:tasks" -> properties.tasks.asJson, + "label:methods" -> properties.methods.asJson, + "label:overviews" -> properties.overviews.asJson + ) ) - ) implicit val decLabelExtensionProperties: Decoder[LabelExtensionProperties] = new Decoder[LabelExtensionProperties] { @@ -70,4 +67,6 @@ object LabelExtensionProperties { } implicit val eqLabelExtensionProperties: Eq[LabelExtensionProperties] = Eq.fromUniversalEquals + + implicit val itemExtensionLabelProperties: ItemExtension[LabelExtensionProperties] = ItemExtension.instance } diff --git a/modules/core/src/main/scala/com/azavea/stac4s/extensions/package.scala b/modules/core/src/main/scala/com/azavea/stac4s/extensions/package.scala new file mode 100644 index 00000000..18ef87b8 --- /dev/null +++ b/modules/core/src/main/scala/com/azavea/stac4s/extensions/package.scala @@ -0,0 +1,13 @@ +package com.azavea.stac4s + +import cats.data.ValidatedNel +import io.circe.Error + +package object extensions { + + // convenience type not to have to write ValidatedNel in a few places / + // to expose a nicer API to users (a la MAML: + // https://github.com/geotrellis/maml/blob/713c6a0c54646d1972855bf5a1f0efddd108f95d/shared/src/main/scala/error/package.scala#L8) + type ExtensionResult[T] = ValidatedNel[Error, T] + +} From a5428907396556bd6af3f563a02d456d203d2a3f Mon Sep 17 00:00:00 2001 From: James Santucci Date: Thu, 14 May 2020 15:24:44 -0500 Subject: [PATCH 02/26] expunge superfluous label:assets on links --- .../scala/com/azavea/stac4s/StacLink.scala | 33 ++++++++++++------- .../catalogs/landsat-stac-layers/catalog.json | 15 +++------ .../2014-153/LC81530252014153LGN00.json | 16 +++------ .../landsat-8-l1/catalog.json | 16 +++------ .../layers/ca/catalog.json | 16 +++------ .../layers/us/catalog.json | 16 +++------ .../catalogs/landsat-stac/catalog.json | 9 ++--- .../2014-153/LC81530252014153LGN00.json | 16 +++------ .../landsat-stac/landsat-8-l1/catalog.json | 16 +++------ .../com/azavea/stac4s/CatalogLayerSpec.scala | 31 ++++++++--------- .../scala/com/azavea/stac4s/CatalogSpec.scala | 25 +++++++------- .../scala/com/azavea/stac4s/Generators.scala | 3 +- .../scala/com/azavea/stac4s/SerDeSpec.scala | 3 +- 13 files changed, 82 insertions(+), 133 deletions(-) diff --git a/modules/core/src/main/scala/com/azavea/stac4s/StacLink.scala b/modules/core/src/main/scala/com/azavea/stac4s/StacLink.scala index 156bff9d..588f8737 100644 --- a/modules/core/src/main/scala/com/azavea/stac4s/StacLink.scala +++ b/modules/core/src/main/scala/com/azavea/stac4s/StacLink.scala @@ -2,24 +2,32 @@ package com.azavea.stac4s import cats.implicits._ import io.circe._ +import io.circe.syntax._ final case class StacLink( href: String, rel: StacLinkType, _type: Option[StacMediaType], title: Option[String], - labelExtAssets: List[String] + extensionFields: JsonObject ) object StacLink { - implicit val encStacLink: Encoder[StacLink] = Encoder.forProduct5( - "href", - "rel", - "type", - "title", - "label:assets" - )(link => (link.href, link.rel, link._type, link.title, link.labelExtAssets)) + implicit val encStacLink: Encoder[StacLink] = new Encoder[StacLink] { + + def apply(link: StacLink): Json = { + val baseEncoder = Encoder + .forProduct4( + "href", + "rel", + "type", + "title" + )((link: StacLink) => (link.href, link.rel, link._type, link.title)) + + baseEncoder(link).deepMerge(link.extensionFields.asJson) + } + } implicit val decStacLink: Decoder[StacLink] = { c: HCursor => ( @@ -27,15 +35,18 @@ object StacLink { c.downField("rel").as[StacLinkType], c.get[Option[StacMediaType]]("type"), c.get[Option[String]]("title"), - c.get[Option[List[String]]]("label:assets") + c.value.as[JsonObject] ).mapN( ( href: String, rel: StacLinkType, _type: Option[StacMediaType], title: Option[String], - assets: Option[List[String]] - ) => StacLink(href, rel, _type, title, assets getOrElse List.empty) + document: JsonObject + ) => + StacLink(href, rel, _type, title, document.filter({ + case (k, _) => !Set("href", "rel", "type", "title").contains(k) + })) ) } } diff --git a/modules/core/src/test/resources/catalogs/landsat-stac-layers/catalog.json b/modules/core/src/test/resources/catalogs/landsat-stac-layers/catalog.json index 93db0fed..d4cc41b2 100644 --- a/modules/core/src/test/resources/catalogs/landsat-stac-layers/catalog.json +++ b/modules/core/src/test/resources/catalogs/landsat-stac-layers/catalog.json @@ -7,28 +7,23 @@ "links" : [ { "href" : "./catalog.json", - "rel" : "self", - "label:assets" : [] + "rel" : "self" }, { "href" : "./catalog.json", - "rel" : "root", - "label:assets" : [] + "rel" : "root" }, { "href" : "./landsat-8-l1/catalog.json", - "rel" : "child", - "label:assets" : [] + "rel" : "child" }, { "href" : "./layers/ca/catalog.json", - "rel" : "child", - "label:assets" : [] + "rel" : "child" }, { "href" : "./layers/us/catalog.json", - "rel" : "child", - "label:assets" : [] + "rel" : "child" } ] } diff --git a/modules/core/src/test/resources/catalogs/landsat-stac-layers/landsat-8-l1/2014-153/LC81530252014153LGN00.json b/modules/core/src/test/resources/catalogs/landsat-stac-layers/landsat-8-l1/2014-153/LC81530252014153LGN00.json index 5261bbdd..7162167e 100644 --- a/modules/core/src/test/resources/catalogs/landsat-stac-layers/landsat-8-l1/2014-153/LC81530252014153LGN00.json +++ b/modules/core/src/test/resources/catalogs/landsat-stac-layers/landsat-8-l1/2014-153/LC81530252014153LGN00.json @@ -44,28 +44,20 @@ "links" : [ { "href" : "../../catalog.json", - "rel" : "root", - "label:assets" : [ - ] + "rel" : "root" }, { "href" : "../catalog.json", - "rel" : "parent", - "label:assets" : [ - ] + "rel" : "parent" }, { "href" : "./LC81530252014153LGN00.json", - "rel" : "self", - "label:assets" : [ - ] + "rel" : "self" }, { "href" : "https://landsatonaws.com/L8/153/025/LC81530252014153LGN0", "rel" : "alternate", - "type" : "text/html", - "label:assets" : [ - ] + "type" : "text/html" } ], "assets" : { diff --git a/modules/core/src/test/resources/catalogs/landsat-stac-layers/landsat-8-l1/catalog.json b/modules/core/src/test/resources/catalogs/landsat-stac-layers/landsat-8-l1/catalog.json index 5f7c3c3f..7fb70822 100644 --- a/modules/core/src/test/resources/catalogs/landsat-stac-layers/landsat-8-l1/catalog.json +++ b/modules/core/src/test/resources/catalogs/landsat-stac-layers/landsat-8-l1/catalog.json @@ -135,27 +135,19 @@ "links" : [ { "href" : "../../catalog.json", - "rel" : "root", - "label:assets" : [ - ] + "rel" : "root" }, { "href" : "../../catalog.json", - "rel" : "parent", - "label:assets" : [ - ] + "rel" : "parent" }, { "href" : "./catalog.json", - "rel" : "self", - "label:assets" : [ - ] + "rel" : "self" }, { "href" : "./2014-153/LC81530252014153LGN00.json", - "rel" : "item", - "label:assets" : [ - ] + "rel" : "item" } ] } diff --git a/modules/core/src/test/resources/catalogs/landsat-stac-layers/layers/ca/catalog.json b/modules/core/src/test/resources/catalogs/landsat-stac-layers/layers/ca/catalog.json index 5119a764..4831425d 100644 --- a/modules/core/src/test/resources/catalogs/landsat-stac-layers/layers/ca/catalog.json +++ b/modules/core/src/test/resources/catalogs/landsat-stac-layers/layers/ca/catalog.json @@ -11,27 +11,19 @@ "links" : [ { "href" : "../catalog.json", - "rel" : "root", - "label:assets" : [ - ] + "rel" : "root" }, { "href" : "../catalog.json", - "rel" : "parent", - "label:assets" : [ - ] + "rel" : "parent" }, { "href" : "./catalog.json", - "rel" : "self", - "label:assets" : [ - ] + "rel" : "self" }, { "href" : "../../landsat-8-l1/2014-153/LC81530252014153LGN00.json", - "rel" : "item", - "label:assets" : [ - ] + "rel" : "item" } ] } diff --git a/modules/core/src/test/resources/catalogs/landsat-stac-layers/layers/us/catalog.json b/modules/core/src/test/resources/catalogs/landsat-stac-layers/layers/us/catalog.json index 443e83d7..fb2e08cd 100644 --- a/modules/core/src/test/resources/catalogs/landsat-stac-layers/layers/us/catalog.json +++ b/modules/core/src/test/resources/catalogs/landsat-stac-layers/layers/us/catalog.json @@ -11,27 +11,19 @@ "links" : [ { "href" : "../catalog.json", - "rel" : "root", - "label:assets" : [ - ] + "rel" : "root" }, { "href" : "../catalog.json", - "rel" : "parent", - "label:assets" : [ - ] + "rel" : "parent" }, { "href" : "./catalog.json", - "rel" : "self", - "label:assets" : [ - ] + "rel" : "self" }, { "href" : "../../landsat-8-l1/2014-153/LC81530252014153LGN00.json", - "rel" : "item", - "label:assets" : [ - ] + "rel" : "item" } ] } diff --git a/modules/core/src/test/resources/catalogs/landsat-stac/catalog.json b/modules/core/src/test/resources/catalogs/landsat-stac/catalog.json index 23bf7e91..8ea41e9b 100644 --- a/modules/core/src/test/resources/catalogs/landsat-stac/catalog.json +++ b/modules/core/src/test/resources/catalogs/landsat-stac/catalog.json @@ -7,18 +7,15 @@ "links" : [ { "href" : "./catalog.json", - "rel" : "self", - "label:assets" : [] + "rel" : "self" }, { "href" : "./catalog.json", - "rel" : "root", - "label:assets" : [] + "rel" : "root" }, { "href" : "./landsat-8-l1/catalog.json", - "rel" : "child", - "label:assets" : [] + "rel" : "child" } ] } diff --git a/modules/core/src/test/resources/catalogs/landsat-stac/landsat-8-l1/2014-153/LC81530252014153LGN00.json b/modules/core/src/test/resources/catalogs/landsat-stac/landsat-8-l1/2014-153/LC81530252014153LGN00.json index 5fee655a..0135797a 100644 --- a/modules/core/src/test/resources/catalogs/landsat-stac/landsat-8-l1/2014-153/LC81530252014153LGN00.json +++ b/modules/core/src/test/resources/catalogs/landsat-stac/landsat-8-l1/2014-153/LC81530252014153LGN00.json @@ -43,28 +43,20 @@ "links" : [ { "href" : "../../catalog.json", - "rel" : "root", - "label:assets" : [ - ] + "rel" : "root" }, { "href" : "../catalog.json", - "rel" : "parent", - "label:assets" : [ - ] + "rel" : "parent" }, { "href" : "./LC81530252014153LGN00.json", - "rel" : "self", - "label:assets" : [ - ] + "rel" : "self" }, { "href" : "https://landsatonaws.com/L8/153/025/LC81530252014153LGN0", "rel" : "alternate", - "type" : "text/html", - "label:assets" : [ - ] + "type" : "text/html" } ], "assets" : { diff --git a/modules/core/src/test/resources/catalogs/landsat-stac/landsat-8-l1/catalog.json b/modules/core/src/test/resources/catalogs/landsat-stac/landsat-8-l1/catalog.json index 5f7c3c3f..7fb70822 100644 --- a/modules/core/src/test/resources/catalogs/landsat-stac/landsat-8-l1/catalog.json +++ b/modules/core/src/test/resources/catalogs/landsat-stac/landsat-8-l1/catalog.json @@ -135,27 +135,19 @@ "links" : [ { "href" : "../../catalog.json", - "rel" : "root", - "label:assets" : [ - ] + "rel" : "root" }, { "href" : "../../catalog.json", - "rel" : "parent", - "label:assets" : [ - ] + "rel" : "parent" }, { "href" : "./catalog.json", - "rel" : "self", - "label:assets" : [ - ] + "rel" : "self" }, { "href" : "./2014-153/LC81530252014153LGN00.json", - "rel" : "item", - "label:assets" : [ - ] + "rel" : "item" } ] } diff --git a/modules/core/src/test/scala/com/azavea/stac4s/CatalogLayerSpec.scala b/modules/core/src/test/scala/com/azavea/stac4s/CatalogLayerSpec.scala index fb873bef..daf3d41b 100644 --- a/modules/core/src/test/scala/com/azavea/stac4s/CatalogLayerSpec.scala +++ b/modules/core/src/test/scala/com/azavea/stac4s/CatalogLayerSpec.scala @@ -27,40 +27,35 @@ class CatalogLayerSpec extends AnyFunSpec with Matchers { rel = StacLinkType.Self, _type = None, title = None, - // should it be an optional thing? - labelExtAssets = Nil + extensionFields = ().asJsonObject ), StacLink( href = "./catalog.json", rel = StacLinkType.StacRoot, _type = None, title = None, - // should it be an optional thing? - labelExtAssets = Nil + extensionFields = ().asJsonObject ), StacLink( href = "./landsat-8-l1/catalog.json", rel = StacLinkType.Child, _type = None, title = None, - // should it be an optional thing? - labelExtAssets = Nil + extensionFields = ().asJsonObject ), StacLink( href = "./layers/ca/catalog.json", rel = StacLinkType.Child, _type = None, title = None, - // should it be an optional thing? - labelExtAssets = Nil + extensionFields = ().asJsonObject ), StacLink( href = "./layers/us/catalog.json", rel = StacLinkType.Child, _type = None, title = None, - // should it be an optional thing? - labelExtAssets = Nil + extensionFields = ().asJsonObject ) ) ) @@ -82,28 +77,28 @@ class CatalogLayerSpec extends AnyFunSpec with Matchers { rel = StacLinkType.StacRoot, _type = None, title = None, - labelExtAssets = Nil + extensionFields = ().asJsonObject ), StacLink( href = "../catalog.json", rel = StacLinkType.Parent, _type = None, title = None, - labelExtAssets = Nil + extensionFields = ().asJsonObject ), StacLink( href = "./catalog.json", rel = StacLinkType.Self, _type = None, title = None, - labelExtAssets = Nil + extensionFields = ().asJsonObject ), StacLink( href = "../../landsat-8-l1/2014-153/LC81530252014153LGN00.json", rel = StacLinkType.Item, _type = None, title = None, - labelExtAssets = Nil + extensionFields = ().asJsonObject ) ) ) @@ -186,21 +181,21 @@ class CatalogLayerSpec extends AnyFunSpec with Matchers { rel = StacLinkType.StacRoot, _type = None, title = None, - labelExtAssets = Nil + extensionFields = ().asJsonObject ), StacLink( href = "../catalog.json", rel = StacLinkType.Parent, _type = None, title = None, - labelExtAssets = Nil + extensionFields = ().asJsonObject ), StacLink( href = "./LC81530252014153LGN00.json", rel = StacLinkType.Self, _type = None, title = None, - labelExtAssets = Nil + extensionFields = ().asJsonObject ), // { "rel":"alternate", "href": "https://landsatonaws.com/L8/153/025/LC81530252014153LGN00", "type": "text/html"}, StacLink( @@ -208,7 +203,7 @@ class CatalogLayerSpec extends AnyFunSpec with Matchers { rel = StacLinkType.Alternate, _type = `text/html`.some, title = None, - labelExtAssets = Nil + extensionFields = ().asJsonObject ) ), assets = Map( diff --git a/modules/core/src/test/scala/com/azavea/stac4s/CatalogSpec.scala b/modules/core/src/test/scala/com/azavea/stac4s/CatalogSpec.scala index b9f234af..1e88febd 100644 --- a/modules/core/src/test/scala/com/azavea/stac4s/CatalogSpec.scala +++ b/modules/core/src/test/scala/com/azavea/stac4s/CatalogSpec.scala @@ -27,24 +27,21 @@ class CatalogSpec extends AnyFunSpec with Matchers { rel = StacLinkType.Self, _type = None, title = None, - // should it be an optional thing? - labelExtAssets = Nil + extensionFields = ().asJsonObject ), StacLink( href = "./catalog.json", rel = StacLinkType.StacRoot, _type = None, title = None, - // should it be an optional thing? - labelExtAssets = Nil + extensionFields = ().asJsonObject ), StacLink( href = "./landsat-8-l1/catalog.json", rel = StacLinkType.Child, _type = None, title = None, - // should it be an optional thing? - labelExtAssets = Nil + extensionFields = ().asJsonObject ) ) ) @@ -175,28 +172,28 @@ class CatalogSpec extends AnyFunSpec with Matchers { rel = StacLinkType.StacRoot, _type = None, title = None, - labelExtAssets = Nil + extensionFields = ().asJsonObject ), StacLink( href = "../../catalog.json", rel = StacLinkType.Parent, _type = None, title = None, - labelExtAssets = Nil + extensionFields = ().asJsonObject ), StacLink( href = "./catalog.json", rel = StacLinkType.Self, _type = None, title = None, - labelExtAssets = Nil + extensionFields = ().asJsonObject ), StacLink( href = "./2014-153/LC81530252014153LGN00.json", rel = StacLinkType.Item, _type = None, title = None, - labelExtAssets = Nil + extensionFields = ().asJsonObject ) ) ) @@ -268,21 +265,21 @@ class CatalogSpec extends AnyFunSpec with Matchers { rel = StacLinkType.StacRoot, _type = None, title = None, - labelExtAssets = Nil + extensionFields = ().asJsonObject ), StacLink( href = "../catalog.json", rel = StacLinkType.Parent, _type = None, title = None, - labelExtAssets = Nil + extensionFields = ().asJsonObject ), StacLink( href = "./LC81530252014153LGN00.json", rel = StacLinkType.Self, _type = None, title = None, - labelExtAssets = Nil + extensionFields = ().asJsonObject ), // { "rel":"alternate", "href": "https://landsatonaws.com/L8/153/025/LC81530252014153LGN00", "type": "text/html"}, StacLink( @@ -290,7 +287,7 @@ class CatalogSpec extends AnyFunSpec with Matchers { rel = StacLinkType.Alternate, _type = `text/html`.some, title = None, - labelExtAssets = Nil + extensionFields = ().asJsonObject ) ), assets = Map( diff --git a/modules/core/src/test/scala/com/azavea/stac4s/Generators.scala b/modules/core/src/test/scala/com/azavea/stac4s/Generators.scala index 9857b9ee..7fbeebe9 100644 --- a/modules/core/src/test/scala/com/azavea/stac4s/Generators.scala +++ b/modules/core/src/test/scala/com/azavea/stac4s/Generators.scala @@ -5,6 +5,7 @@ import cats.data.NonEmptyList import cats.implicits._ import geotrellis.vector.{Geometry, Point, Polygon} import io.circe.JsonObject +import io.circe.syntax._ import org.scalacheck._ import org.scalacheck.Arbitrary.arbitrary import org.scalacheck.cats.implicits._ @@ -124,7 +125,7 @@ object Generators { Gen.const(StacLinkType.Self), // self link type is required by TMS reification Gen.option(mediaTypeGen), Gen.option(nonEmptyStringGen), - Gen.nonEmptyListOf[String](arbitrary[String]) + Gen.const(().asJsonObject) ).mapN(StacLink.apply) private def temporalExtentGen: Gen[TemporalExtent] = { diff --git a/modules/core/src/test/scala/com/azavea/stac4s/SerDeSpec.scala b/modules/core/src/test/scala/com/azavea/stac4s/SerDeSpec.scala index d386d541..00706071 100644 --- a/modules/core/src/test/scala/com/azavea/stac4s/SerDeSpec.scala +++ b/modules/core/src/test/scala/com/azavea/stac4s/SerDeSpec.scala @@ -4,6 +4,7 @@ import com.azavea.stac4s.extensions.label._ import com.azavea.stac4s.meta._ import Generators._ import geotrellis.vector.Geometry +import io.circe.syntax._ import io.circe.parser._ import io.circe.testing.{ArbitraryInstances, CodecTests} import org.scalatest.funsuite.AnyFunSuite @@ -58,6 +59,6 @@ class SerDeSpec extends AnyFunSuite with FunSuiteDiscipline with Checkers with M test("ignore optional fields") { val link = decode[StacLink]("""{"href":"s3://foo/item.json","rel":"item"}""") - link map { _.labelExtAssets } shouldBe Right(List.empty[String]) + link map { _.extensionFields } shouldBe Right(().asJsonObject) } } From dbfed6d3e0d9df6f5fd1b8a39182e6315322fd81 Mon Sep 17 00:00:00 2001 From: James Santucci Date: Thu, 14 May 2020 15:47:02 -0500 Subject: [PATCH 03/26] add linkextension typeclass, evidence for LabelLinkExtension --- .../stac4s/extensions/LinkExtension.scala | 26 +++++++++++++++++++ ...perties.scala => LabelItemExtension.scala} | 16 ++++++------ .../extensions/label/LabelLinkExtension.scala | 21 +++++++++++++++ .../scala/com/azavea/stac4s/Generators.scala | 6 ++--- .../scala/com/azavea/stac4s/SerDeSpec.scala | 2 +- 5 files changed, 59 insertions(+), 12 deletions(-) create mode 100644 modules/core/src/main/scala/com/azavea/stac4s/extensions/LinkExtension.scala rename modules/core/src/main/scala/com/azavea/stac4s/extensions/label/{LabelExtensionProperties.scala => LabelItemExtension.scala} (81%) create mode 100644 modules/core/src/main/scala/com/azavea/stac4s/extensions/label/LabelLinkExtension.scala diff --git a/modules/core/src/main/scala/com/azavea/stac4s/extensions/LinkExtension.scala b/modules/core/src/main/scala/com/azavea/stac4s/extensions/LinkExtension.scala new file mode 100644 index 00000000..3d35c626 --- /dev/null +++ b/modules/core/src/main/scala/com/azavea/stac4s/extensions/LinkExtension.scala @@ -0,0 +1,26 @@ +package com.azavea.stac4s.extensions + +import com.azavea.stac4s.StacLink + +import io.circe.{Decoder, Encoder} +import io.circe.syntax._ + +trait LinkExtension[T] { + def getProperties(link: StacLink): ExtensionResult[T] + + def extend(link: StacLink, extensionFields: T): StacLink +} + +object LinkExtension { + def apply[T](implicit ev: LinkExtension[T]): LinkExtension[T] = ev + + def instance[T](implicit decoder: Decoder[T], objectEncoder: Encoder.AsObject[T]) = + new LinkExtension[T] { + + def getProperties(link: StacLink): ExtensionResult[T] = + decoder.decodeAccumulating(link.extensionFields.asJson.hcursor) + + def extend(link: StacLink, extensionFields: T): StacLink = + link.copy(extensionFields = link.extensionFields.deepMerge(objectEncoder.encodeObject(extensionFields))) + } +} diff --git a/modules/core/src/main/scala/com/azavea/stac4s/extensions/label/LabelExtensionProperties.scala b/modules/core/src/main/scala/com/azavea/stac4s/extensions/label/LabelItemExtension.scala similarity index 81% rename from modules/core/src/main/scala/com/azavea/stac4s/extensions/label/LabelExtensionProperties.scala rename to modules/core/src/main/scala/com/azavea/stac4s/extensions/label/LabelItemExtension.scala index 0608dd92..2907b009 100644 --- a/modules/core/src/main/scala/com/azavea/stac4s/extensions/label/LabelExtensionProperties.scala +++ b/modules/core/src/main/scala/com/azavea/stac4s/extensions/label/LabelItemExtension.scala @@ -7,7 +7,7 @@ import cats.implicits._ import io.circe.{Decoder, Encoder, HCursor, Json} import io.circe.syntax._ -case class LabelExtensionProperties( +case class LabelItemExtension( properties: LabelProperties, classes: List[LabelClass], description: String, @@ -17,11 +17,11 @@ case class LabelExtensionProperties( overviews: List[LabelOverview] ) -object LabelExtensionProperties { +object LabelItemExtension { - implicit val encLabelExtensionPropertiesObject: Encoder.AsObject[LabelExtensionProperties] = Encoder + implicit val encLabelExtensionPropertiesObject: Encoder.AsObject[LabelItemExtension] = Encoder .AsObject[Map[String, Json]] - .contramapObject((properties: LabelExtensionProperties) => + .contramapObject((properties: LabelItemExtension) => Map( "label:properties" -> properties.properties.asJson, "label:classes" -> properties.classes.asJson, @@ -33,7 +33,7 @@ object LabelExtensionProperties { ) ) - implicit val decLabelExtensionProperties: Decoder[LabelExtensionProperties] = new Decoder[LabelExtensionProperties] { + implicit val decLabelExtensionProperties: Decoder[LabelItemExtension] = new Decoder[LabelItemExtension] { def apply(c: HCursor) = ( @@ -54,7 +54,7 @@ object LabelExtensionProperties { methods: Option[List[LabelMethod]], overviews: Option[List[LabelOverview]] ) => - LabelExtensionProperties( + LabelItemExtension( properties, classes, description, @@ -66,7 +66,7 @@ object LabelExtensionProperties { ) } - implicit val eqLabelExtensionProperties: Eq[LabelExtensionProperties] = Eq.fromUniversalEquals + implicit val eqLabelExtensionProperties: Eq[LabelItemExtension] = Eq.fromUniversalEquals - implicit val itemExtensionLabelProperties: ItemExtension[LabelExtensionProperties] = ItemExtension.instance + implicit val itemExtensionLabelProperties: ItemExtension[LabelItemExtension] = ItemExtension.instance } diff --git a/modules/core/src/main/scala/com/azavea/stac4s/extensions/label/LabelLinkExtension.scala b/modules/core/src/main/scala/com/azavea/stac4s/extensions/label/LabelLinkExtension.scala new file mode 100644 index 00000000..c94531c5 --- /dev/null +++ b/modules/core/src/main/scala/com/azavea/stac4s/extensions/label/LabelLinkExtension.scala @@ -0,0 +1,21 @@ +package com.azavea.stac4s.extensions.label + +import com.azavea.stac4s.extensions.LinkExtension + +import cats.data.NonEmptyList +import io.circe.{Decoder, Encoder, Json} +import io.circe.syntax._ + +case class LabelLinkExtension(assets: NonEmptyList[String]) + +object LabelLinkExtension { + + implicit val encLabelLinkExtensionObject: Encoder.AsObject[LabelLinkExtension] = Encoder + .AsObject[Map[String, Json]] + .contramapObject((extensionFields: LabelLinkExtension) => Map("label:assets" -> extensionFields.assets.asJson)) + + implicit val decLabelLinkExtension: Decoder[LabelLinkExtension] = + Decoder.forProduct1("label:assets")(LabelLinkExtension.apply) + + implicit val linkExtension: LinkExtension[LabelLinkExtension] = LinkExtension.instance +} diff --git a/modules/core/src/test/scala/com/azavea/stac4s/Generators.scala b/modules/core/src/test/scala/com/azavea/stac4s/Generators.scala index 7fbeebe9..84733faa 100644 --- a/modules/core/src/test/scala/com/azavea/stac4s/Generators.scala +++ b/modules/core/src/test/scala/com/azavea/stac4s/Generators.scala @@ -308,7 +308,7 @@ object Generators { private def layerPropertiesGen: Gen[LayerProperties] = Gen.listOf(nonEmptyStringGen).map(LayerProperties.apply) - private def labelExtensionPropertiesGen: Gen[LabelExtensionProperties] = + private def labelExtensionPropertiesGen: Gen[LabelItemExtension] = ( labelPropertiesGen, Gen.listOf(labelClassGen), @@ -317,7 +317,7 @@ object Generators { Gen.listOf(labelTaskGen), Gen.listOf(labelMethodGen), Gen.listOf(labelOverviewGen) - ).mapN(LabelExtensionProperties.apply) + ).mapN(LabelItemExtension.apply) implicit val arbMediaType: Arbitrary[StacMediaType] = Arbitrary { mediaTypeGen @@ -395,7 +395,7 @@ object Generators { implicit val arbLabelProperties: Arbitrary[LabelProperties] = Arbitrary { labelPropertiesGen } - implicit val arbLabelExtensionProperties: Arbitrary[LabelExtensionProperties] = Arbitrary { + implicit val arbLabelExtensionProperties: Arbitrary[LabelItemExtension] = Arbitrary { labelExtensionPropertiesGen } diff --git a/modules/core/src/test/scala/com/azavea/stac4s/SerDeSpec.scala b/modules/core/src/test/scala/com/azavea/stac4s/SerDeSpec.scala index 00706071..9352440d 100644 --- a/modules/core/src/test/scala/com/azavea/stac4s/SerDeSpec.scala +++ b/modules/core/src/test/scala/com/azavea/stac4s/SerDeSpec.scala @@ -44,7 +44,7 @@ class SerDeSpec extends AnyFunSuite with FunSuiteDiscipline with Checkers with M checkAll("Codec.LabelClassClasses", CodecTests[LabelClassClasses].unserializableCodec) checkAll("Codec.LabelClassName", CodecTests[LabelClassName].unserializableCodec) checkAll("Codec.LabelCount", CodecTests[LabelCount].unserializableCodec) - checkAll("Codec.LabelExtensionProperties", CodecTests[LabelExtensionProperties].unserializableCodec) + checkAll("Codec.LabelExtensionProperties", CodecTests[LabelItemExtension].unserializableCodec) checkAll("Codec.LabelMethod", CodecTests[LabelMethod].unserializableCodec) checkAll("Codec.LabelOverview", CodecTests[LabelOverview].unserializableCodec) checkAll("Codec.LabelProperties", CodecTests[LabelProperties].unserializableCodec) From a28eccdb2c21d95b4c3d071948a7840cc3274a6a Mon Sep 17 00:00:00 2001 From: James Santucci Date: Thu, 14 May 2020 15:50:06 -0500 Subject: [PATCH 04/26] add item extension evidence to layer extension, rename --- .../extensions/layer/LayerItemExtension.scala | 25 +++++++++++++++++++ .../extensions/layer/LayerProperties.scala | 22 ---------------- .../com/azavea/stac4s/CatalogLayerSpec.scala | 2 +- .../scala/com/azavea/stac4s/Generators.scala | 8 +++--- .../scala/com/azavea/stac4s/SerDeSpec.scala | 4 +-- 5 files changed, 32 insertions(+), 29 deletions(-) create mode 100644 modules/core/src/main/scala/com/azavea/stac4s/extensions/layer/LayerItemExtension.scala delete mode 100644 modules/core/src/main/scala/com/azavea/stac4s/extensions/layer/LayerProperties.scala diff --git a/modules/core/src/main/scala/com/azavea/stac4s/extensions/layer/LayerItemExtension.scala b/modules/core/src/main/scala/com/azavea/stac4s/extensions/layer/LayerItemExtension.scala new file mode 100644 index 00000000..0b3cbaa8 --- /dev/null +++ b/modules/core/src/main/scala/com/azavea/stac4s/extensions/layer/LayerItemExtension.scala @@ -0,0 +1,25 @@ +package com.azavea.stac4s.extensions.layer + +import com.azavea.stac4s.extensions.ItemExtension + +import cats.Eq +import io.circe.{Decoder, Encoder} +import io.circe.syntax._ + +case class LayerItemExtension(ids: List[String]) + +object LayerItemExtension { + implicit val eqLayerProperties: Eq[LayerItemExtension] = Eq.fromUniversalEquals + + implicit val encLayerProperties: Encoder.AsObject[LayerItemExtension] = + Encoder.AsObject.instance[LayerItemExtension] { o => Map("layer:ids" -> o.ids.asJson).asJsonObject } + + implicit val decLayerProperties: Decoder[LayerItemExtension] = Decoder[Map[String, List[String]]] emap { + _.get("layer:ids") match { + case Some(l) => Right(LayerItemExtension(l)) + case _ => Left("Could not decode LayerProperties.") + } + } + + implicit val itemExtension: ItemExtension[LayerItemExtension] = ItemExtension.instance +} diff --git a/modules/core/src/main/scala/com/azavea/stac4s/extensions/layer/LayerProperties.scala b/modules/core/src/main/scala/com/azavea/stac4s/extensions/layer/LayerProperties.scala deleted file mode 100644 index ac87b122..00000000 --- a/modules/core/src/main/scala/com/azavea/stac4s/extensions/layer/LayerProperties.scala +++ /dev/null @@ -1,22 +0,0 @@ -package com.azavea.stac4s.extensions.layer - -import cats.Eq -import io.circe.{Decoder, Encoder} -import io.circe.syntax._ - -case class LayerProperties(ids: List[String]) - -object LayerProperties { - implicit val eqLayerProperties: Eq[LayerProperties] = Eq.fromUniversalEquals - - implicit val encLayerProperties: Encoder.AsObject[LayerProperties] = Encoder.AsObject.instance[LayerProperties] { o => - Map("layer:ids" -> o.ids.asJson).asJsonObject - } - - implicit val decLayerProperties: Decoder[LayerProperties] = Decoder[Map[String, List[String]]] emap { - _.get("layer:ids") match { - case Some(l) => Right(LayerProperties(l)) - case _ => Left("Could not decode LayerProperties.") - } - } -} diff --git a/modules/core/src/test/scala/com/azavea/stac4s/CatalogLayerSpec.scala b/modules/core/src/test/scala/com/azavea/stac4s/CatalogLayerSpec.scala index daf3d41b..2b9a018e 100644 --- a/modules/core/src/test/scala/com/azavea/stac4s/CatalogLayerSpec.scala +++ b/modules/core/src/test/scala/com/azavea/stac4s/CatalogLayerSpec.scala @@ -174,7 +174,7 @@ class CatalogLayerSpec extends AnyFunSpec with Matchers { "landsat:geometric_rmse_model_y" -> 4.654.asJson, "landsat:geometric_rmse_verify" -> 5.364.asJson, "landsat:image_quality_oli" -> 9.asJson - ).asJsonObject.deepMerge(LayerProperties(List(layerUS.id, layerCA.id)).asJsonObject), // layer extension + ).asJsonObject.deepMerge(LayerItemExtension(List(layerUS.id, layerCA.id)).asJsonObject), // layer extension links = List( StacLink( href = "../../catalog.json", diff --git a/modules/core/src/test/scala/com/azavea/stac4s/Generators.scala b/modules/core/src/test/scala/com/azavea/stac4s/Generators.scala index 84733faa..f4744138 100644 --- a/modules/core/src/test/scala/com/azavea/stac4s/Generators.scala +++ b/modules/core/src/test/scala/com/azavea/stac4s/Generators.scala @@ -14,7 +14,7 @@ import java.time.Instant import com.github.tbouron.SpdxLicense import com.azavea.stac4s.extensions.label.LabelClassClasses.NamedLabelClasses import com.azavea.stac4s.extensions.label.LabelClassClasses.NumberedLabelClasses -import com.azavea.stac4s.extensions.layer.LayerProperties +import com.azavea.stac4s.extensions.layer.LayerItemExtension object Generators { @@ -305,8 +305,8 @@ object Generators { private def labelPropertiesGen: Gen[LabelProperties] = Gen.option(Gen.listOf(nonEmptyStringGen)).map(LabelProperties.fromOption) - private def layerPropertiesGen: Gen[LayerProperties] = - Gen.listOf(nonEmptyStringGen).map(LayerProperties.apply) + private def layerPropertiesGen: Gen[LayerItemExtension] = + Gen.listOf(nonEmptyStringGen).map(LayerItemExtension.apply) private def labelExtensionPropertiesGen: Gen[LabelItemExtension] = ( @@ -399,5 +399,5 @@ object Generators { labelExtensionPropertiesGen } - implicit val arbLayerProperties: Arbitrary[LayerProperties] = Arbitrary { layerPropertiesGen } + implicit val arbLayerProperties: Arbitrary[LayerItemExtension] = Arbitrary { layerPropertiesGen } } diff --git a/modules/core/src/test/scala/com/azavea/stac4s/SerDeSpec.scala b/modules/core/src/test/scala/com/azavea/stac4s/SerDeSpec.scala index 9352440d..d778a322 100644 --- a/modules/core/src/test/scala/com/azavea/stac4s/SerDeSpec.scala +++ b/modules/core/src/test/scala/com/azavea/stac4s/SerDeSpec.scala @@ -13,7 +13,7 @@ import org.scalatestplus.scalacheck.Checkers import org.typelevel.discipline.scalatest.FunSuiteDiscipline import java.time.Instant -import com.azavea.stac4s.extensions.layer.LayerProperties +import com.azavea.stac4s.extensions.layer.LayerItemExtension class SerDeSpec extends AnyFunSuite with FunSuiteDiscipline with Checkers with Matchers with ArbitraryInstances { @@ -53,7 +53,7 @@ class SerDeSpec extends AnyFunSuite with FunSuiteDiscipline with Checkers with M checkAll("Codec.LabelType", CodecTests[LabelType].unserializableCodec) // Layer extension - checkAll("Codec.LayerProperties", CodecTests[LayerProperties].unserializableCodec) + checkAll("Codec.LayerProperties", CodecTests[LayerItemExtension].unserializableCodec) // unit tests test("ignore optional fields") { From 6228757f4050fcf83c938310fac7d42d2881b7d7 Mon Sep 17 00:00:00 2001 From: James Santucci Date: Thu, 14 May 2020 15:56:47 -0500 Subject: [PATCH 05/26] add label link extension to serdespec for links --- .../src/test/scala/com/azavea/stac4s/Generators.scala | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/modules/core/src/test/scala/com/azavea/stac4s/Generators.scala b/modules/core/src/test/scala/com/azavea/stac4s/Generators.scala index f4744138..149c9df7 100644 --- a/modules/core/src/test/scala/com/azavea/stac4s/Generators.scala +++ b/modules/core/src/test/scala/com/azavea/stac4s/Generators.scala @@ -37,6 +37,15 @@ object Generators { private def instantGen: Gen[Instant] = arbitrary[Int] map { x => Instant.now.plusMillis(x.toLong) } + private def linkExtensionFields: Gen[JsonObject] = Gen.oneOf( + Gen.const(().asJsonObject), + Gen + .nonEmptyListOf(nonEmptyStringGen) + .map(NonEmptyList.fromListUnsafe) + .map(LabelLinkExtension.apply) + .map(_.asJsonObject) + ) + private def mediaTypeGen: Gen[StacMediaType] = Gen.oneOf( `image/tiff`, `image/vnd.stac.geotiff`, @@ -125,7 +134,7 @@ object Generators { Gen.const(StacLinkType.Self), // self link type is required by TMS reification Gen.option(mediaTypeGen), Gen.option(nonEmptyStringGen), - Gen.const(().asJsonObject) + linkExtensionFields ).mapN(StacLink.apply) private def temporalExtentGen: Gen[TemporalExtent] = { From d0072148b4f381018f5c9d4b6dec24af0154a34b Mon Sep 17 00:00:00 2001 From: James Santucci Date: Thu, 14 May 2020 16:08:50 -0500 Subject: [PATCH 06/26] add catalog and asset extensions --- .../scala/com/azavea/stac4s/StacCatalog.scala | 66 +++++++++++++++---- .../com/azavea/stac4s/StacItemAsset.scala | 44 ++++++++++--- .../stac4s/extensions/AssetExtension.scala | 25 +++++++ .../stac4s/extensions/CatalogExtension.scala | 25 +++++++ 4 files changed, 139 insertions(+), 21 deletions(-) create mode 100644 modules/core/src/main/scala/com/azavea/stac4s/extensions/AssetExtension.scala create mode 100644 modules/core/src/main/scala/com/azavea/stac4s/extensions/CatalogExtension.scala diff --git a/modules/core/src/main/scala/com/azavea/stac4s/StacCatalog.scala b/modules/core/src/main/scala/com/azavea/stac4s/StacCatalog.scala index efd037d5..fbbb3309 100644 --- a/modules/core/src/main/scala/com/azavea/stac4s/StacCatalog.scala +++ b/modules/core/src/main/scala/com/azavea/stac4s/StacCatalog.scala @@ -1,7 +1,9 @@ package com.azavea.stac4s import cats.Eq +import cats.implicits._ import io.circe._ +import io.circe.syntax._ final case class StacCatalog( stacVersion: String, @@ -9,25 +11,63 @@ final case class StacCatalog( id: String, title: Option[String], description: String, - links: List[StacLink] + links: List[StacLink], + extensionFields: JsonObject ) object StacCatalog { implicit val eqStacCatalog: Eq[StacCatalog] = Eq.fromUniversalEquals - implicit val encCatalog: Encoder[StacCatalog] = - Encoder.forProduct6("stac_version", "stac_extensions", "id", "title", "description", "links")(catalog => + implicit val encCatalog: Encoder[StacCatalog] = new Encoder[StacCatalog] { + + def apply(catalog: StacCatalog): Json = { + val baseEncoder: Encoder[StacCatalog] = + Encoder.forProduct6("stac_version", "stac_extensions", "id", "title", "description", "links")(catalog => + ( + catalog.stacVersion, + catalog.stacExtensions, + catalog.id, + catalog.title, + catalog.description, + catalog.links + ) + ) + + baseEncoder(catalog).deepMerge(catalog.extensionFields.asJson) + } + } + + implicit val decCatalog: Decoder[StacCatalog] = { c: HCursor => + ( + c.get[String]("stac_version"), + c.get[List[String]]("stac_extensions"), + c.get[String]("id"), + c.get[Option[String]]("title"), + c.get[String]("description"), + c.get[List[StacLink]]("links"), + c.value.as[JsonObject] + ).mapN( ( - catalog.stacVersion, - catalog.stacExtensions, - catalog.id, - catalog.title, - catalog.description, - catalog.links - ) + version: String, + extensions: List[String], + id: String, + title: Option[String], + description: String, + links: List[StacLink], + document: JsonObject + ) => + StacCatalog.apply( + version, + extensions, + id, + title, + description, + links, + document.filter({ + case (k, _) => !Set("stac_version", "stac_extensions", "id", "title", "description", "links").contains(k) + }) + ) ) - - implicit val decCatalog: Decoder[StacCatalog] = - Decoder.forProduct6("stac_version", "stac_extensions", "id", "title", "description", "links")(StacCatalog.apply) + } } diff --git a/modules/core/src/main/scala/com/azavea/stac4s/StacItemAsset.scala b/modules/core/src/main/scala/com/azavea/stac4s/StacItemAsset.scala index 3bd7141c..bcef6422 100644 --- a/modules/core/src/main/scala/com/azavea/stac4s/StacItemAsset.scala +++ b/modules/core/src/main/scala/com/azavea/stac4s/StacItemAsset.scala @@ -1,27 +1,55 @@ package com.azavea.stac4s import cats.Eq +import cats.implicits._ import io.circe._ +import io.circe.syntax._ final case class StacItemAsset( href: String, title: Option[String], description: Option[String], roles: List[StacAssetRole], - _type: Option[StacMediaType] + _type: Option[StacMediaType], + extensionFields: JsonObject ) object StacItemAsset { implicit val eqStacItemAsset: Eq[StacItemAsset] = Eq.fromUniversalEquals - implicit val encStacItemAsset: Encoder[StacItemAsset] = - Encoder.forProduct5("href", "title", "description", "roles", "type")(asset => - (asset.href, asset.title, asset.description, asset.roles, asset._type) - ) + implicit val encStacItemAsset: Encoder[StacItemAsset] = new Encoder[StacItemAsset] { + + def apply(asset: StacItemAsset): Json = { + + val baseEncoder: Encoder[StacItemAsset] = + Encoder.forProduct5("href", "title", "description", "roles", "type")(asset => + (asset.href, asset.title, asset.description, asset.roles, asset._type) + ) + baseEncoder(asset).deepMerge(asset.extensionFields.asJson) + } + } - implicit val decStacItemAsset: Decoder[StacItemAsset] = - Decoder.forProduct5("href", "title", "description", "roles", "type")( - StacItemAsset.apply + implicit val decStacItemAsset: Decoder[StacItemAsset] = { c: HCursor => + ( + c.get[String]("href"), + c.get[Option[String]]("title"), + c.get[Option[String]]("description"), + c.get[List[StacAssetRole]]("roles"), + c.get[Option[StacMediaType]]("type"), + c.value.as[JsonObject] + ).mapN( + ( + href: String, + title: Option[String], + description: Option[String], + roles: List[StacAssetRole], + mediaType: Option[StacMediaType], + document: JsonObject + ) => + StacItemAsset(href, title, description, roles, mediaType, document.filter({ + case (k, _) => !Set("href", "title", "description", "roles", "type").contains(k) + })) ) + } } diff --git a/modules/core/src/main/scala/com/azavea/stac4s/extensions/AssetExtension.scala b/modules/core/src/main/scala/com/azavea/stac4s/extensions/AssetExtension.scala new file mode 100644 index 00000000..38f85879 --- /dev/null +++ b/modules/core/src/main/scala/com/azavea/stac4s/extensions/AssetExtension.scala @@ -0,0 +1,25 @@ +package com.azavea.stac4s.extensions + +import com.azavea.stac4s.StacItemAsset + +import io.circe.{Decoder, Encoder} +import io.circe.syntax._ + +trait AssetExtension[T] { + def getProperties(asset: StacItemAsset): ExtensionResult[T] + def extend(asset: StacItemAsset, extensionFields: T): StacItemAsset +} + +object AssetExtension { + def apply[T](implicit ev: AssetExtension[T]): AssetExtension[T] = ev + + def instance[T](implicit decoder: Decoder[T], objectEncoder: Encoder.AsObject[T]) = + new AssetExtension[T] { + + def getProperties(asset: StacItemAsset): ExtensionResult[T] = + decoder.decodeAccumulating(asset.extensionFields.asJson.hcursor) + + def extend(asset: StacItemAsset, extensionFields: T): StacItemAsset = + asset.copy(extensionFields = asset.extensionFields.deepMerge(objectEncoder.encodeObject(extensionFields))) + } +} diff --git a/modules/core/src/main/scala/com/azavea/stac4s/extensions/CatalogExtension.scala b/modules/core/src/main/scala/com/azavea/stac4s/extensions/CatalogExtension.scala new file mode 100644 index 00000000..9dea4a7b --- /dev/null +++ b/modules/core/src/main/scala/com/azavea/stac4s/extensions/CatalogExtension.scala @@ -0,0 +1,25 @@ +package com.azavea.stac4s.extensions + +import com.azavea.stac4s.StacCatalog + +import io.circe.{Decoder, Encoder} +import io.circe.syntax._ + +trait CatalogExtension[T] { + def getProperties(catalog: StacCatalog): ExtensionResult[T] + def extend(catalog: StacCatalog, extensionFields: T): StacCatalog +} + +object CatalogExtension { + def apply[T](implicit ev: CatalogExtension[T]): CatalogExtension[T] = ev + + def instance[T](implicit decoder: Decoder[T], objectEncoder: Encoder.AsObject[T]) = + new CatalogExtension[T] { + + def getProperties(catalog: StacCatalog): ExtensionResult[T] = + decoder.decodeAccumulating(catalog.extensionFields.asJson.hcursor) + + def extend(catalog: StacCatalog, extensionFields: T): StacCatalog = + catalog.copy(extensionFields = catalog.extensionFields.deepMerge(objectEncoder.encodeObject(extensionFields))) + } +} From 949f318537ccf2ab6ebfc4fedcbb7e8f086d9d18 Mon Sep 17 00:00:00 2001 From: James Santucci Date: Thu, 14 May 2020 16:59:40 -0500 Subject: [PATCH 07/26] add rest of extensions / tests (failing) --- .../com/azavea/stac4s/StacCollection.scala | 130 +++++++++++------- .../com/azavea/stac4s/CatalogLayerSpec.scala | 45 ++++-- .../scala/com/azavea/stac4s/CatalogSpec.scala | 46 +++++-- .../scala/com/azavea/stac4s/Generators.scala | 10 +- 4 files changed, 150 insertions(+), 81 deletions(-) diff --git a/modules/core/src/main/scala/com/azavea/stac4s/StacCollection.scala b/modules/core/src/main/scala/com/azavea/stac4s/StacCollection.scala index 86286bde..b4bf0fe8 100644 --- a/modules/core/src/main/scala/com/azavea/stac4s/StacCollection.scala +++ b/modules/core/src/main/scala/com/azavea/stac4s/StacCollection.scala @@ -1,8 +1,10 @@ package com.azavea.stac4s import cats.Eq +import cats.implicits._ import geotrellis.vector.{io => _} import io.circe._ +import io.circe.syntax._ final case class StacCollection( stacVersion: String, @@ -14,60 +16,87 @@ final case class StacCollection( license: StacLicense, providers: List[StacProvider], extent: StacExtent, + summaries: JsonObject, properties: JsonObject, - links: List[StacLink] + links: List[StacLink], + extensionFields: JsonObject ) object StacCollection { + val collectionFields = Set( + "stac_version", + "stac_extensions", + "id", + "title", + "description", + "keywords", + "license", + "providers", + "extent", + "summaries", + "properties", + "links" + ) + implicit val eqStacCollection: Eq[StacCollection] = Eq.fromUniversalEquals - implicit val encoderStacCollection: Encoder[StacCollection] = - Encoder.forProduct11( - "stac_version", - "stac_extensions", - "id", - "title", - "description", - "keywords", - "license", - "providers", - "extent", - "properties", - "links" - )(collection => - ( - collection.stacVersion, - collection.stacExtensions, - collection.id, - collection.title, - collection.description, - collection.keywords, - collection.license, - collection.providers, - collection.extent, - collection.properties, - collection.links + implicit val encoderStacCollection: Encoder[StacCollection] = new Encoder[StacCollection] { + + def apply(collection: StacCollection): Json = { + val baseEncoder: Encoder[StacCollection] = Encoder.forProduct12( + "stac_version", + "stac_extensions", + "id", + "title", + "description", + "keywords", + "license", + "providers", + "extent", + "summaries", + "properties", + "links" + )(collection => + ( + collection.stacVersion, + collection.stacExtensions, + collection.id, + collection.title, + collection.description, + collection.keywords, + collection.license, + collection.providers, + collection.extent, + collection.summaries, + collection.properties, + collection.links + ) ) - ) - implicit val decoderStacCollection: Decoder[StacCollection] = - Decoder.forProduct11( - "stac_version", - "stac_extensions", - "id", - "title", - "description", - "keywords", - "license", - "providers", - "extent", - "properties", - "links" - )( + baseEncoder(collection).deepMerge(collection.extensionFields.asJson) + } + } + + implicit val decoderStacCollection: Decoder[StacCollection] = { c: HCursor => + ( + c.get[String]("stac_version"), + c.get[Option[List[String]]]("stac_extensions"), + c.get[String]("id"), + c.get[Option[String]]("title"), + c.get[String]("description"), + c.get[Option[List[String]]]("keywords"), + c.get[StacLicense]("license"), + c.get[Option[List[StacProvider]]]("providers"), + c.get[StacExtent]("extent"), + c.get[Option[JsonObject]]("summaries"), + c.get[JsonObject]("properties"), + c.get[List[StacLink]]("links"), + c.value.as[JsonObject] + ).mapN( ( stacVersion: String, - stacExtensions: List[String], + stacExtensions: Option[List[String]], id: String, title: Option[String], description: String, @@ -75,12 +104,14 @@ object StacCollection { license: StacLicense, providers: Option[List[StacProvider]], extent: StacExtent, - properties: Option[JsonObject], - links: List[StacLink] + summaries: Option[JsonObject], + properties: JsonObject, + links: List[StacLink], + extensionFields: JsonObject ) => StacCollection( stacVersion, - stacExtensions, + stacExtensions getOrElse Nil, id, title, description, @@ -88,8 +119,11 @@ object StacCollection { license, providers getOrElse List.empty, extent, - properties getOrElse JsonObject.fromMap(Map.empty), - links + summaries getOrElse JsonObject.fromMap(Map.empty), + properties, + links, + extensionFields ) ) + } } diff --git a/modules/core/src/test/scala/com/azavea/stac4s/CatalogLayerSpec.scala b/modules/core/src/test/scala/com/azavea/stac4s/CatalogLayerSpec.scala index 2b9a018e..a8726ef0 100644 --- a/modules/core/src/test/scala/com/azavea/stac4s/CatalogLayerSpec.scala +++ b/modules/core/src/test/scala/com/azavea/stac4s/CatalogLayerSpec.scala @@ -57,7 +57,8 @@ class CatalogLayerSpec extends AnyFunSpec with Matchers { title = None, extensionFields = ().asJsonObject ) - ) + ), + extensionFields = ().asJsonObject ) root.asJson.deepDropNullValues shouldBe getJson("/catalogs/landsat-stac-layers/catalog.json") @@ -100,7 +101,8 @@ class CatalogLayerSpec extends AnyFunSpec with Matchers { title = None, extensionFields = ().asJsonObject ) - ) + ), + extensionFields = ().asJsonObject ) val layerCA = layerUS.copy( @@ -213,21 +215,24 @@ class CatalogLayerSpec extends AnyFunSpec with Matchers { title = "Thumbnail".some, description = "A medium sized thumbnail".some, roles = List(StacAssetRole.Thumbnail), - _type = `image/jpeg`.some + _type = `image/jpeg`.some, + extensionFields = ().asJsonObject ), "metadata" -> StacItemAsset( href = "http://landsat-pds.s3.amazonaws.com/L8/153/025/LC81530252014153LGN00/LC81530252014153LGN00_MTL.txt", title = "Original Metadata".some, description = "The original MTL metadata file provided for each Landsat scene".some, roles = List(StacAssetRole.Metadata), - _type = VendorMediaType("mtl").some + _type = VendorMediaType("mtl").some, + extensionFields = ().asJsonObject ), "B1" -> StacItemAsset( href = "http://landsat-pds.s3.amazonaws.com/L8/153/025/LC81530252014153LGN00/LC81530252014153LGN00_B1.TIF", title = "Coastal Band (B1)".some, description = "Coastal Band Top Of the Atmosphere".some, roles = Nil, - _type = `image/tiff`.some + _type = `image/tiff`.some, + extensionFields = ().asJsonObject // "eo:bands": [0], ), "B2" -> StacItemAsset( @@ -235,7 +240,8 @@ class CatalogLayerSpec extends AnyFunSpec with Matchers { title = "Blue Band (B2)".some, description = "Blue Band Top Of the Atmosphere".some, roles = Nil, - _type = `image/tiff`.some + _type = `image/tiff`.some, + extensionFields = ().asJsonObject // "eo:bands": [1], ), "B3" -> StacItemAsset( @@ -243,7 +249,8 @@ class CatalogLayerSpec extends AnyFunSpec with Matchers { title = "Green Band (B3)".some, description = "Green Band Top Of the Atmosphere".some, roles = Nil, - _type = `image/tiff`.some + _type = `image/tiff`.some, + extensionFields = ().asJsonObject // "eo:bands": [2], ), "B4" -> StacItemAsset( @@ -251,7 +258,8 @@ class CatalogLayerSpec extends AnyFunSpec with Matchers { title = "Red Band (B4)".some, description = "Red Band Top Of the Atmosphere".some, roles = Nil, - _type = `image/tiff`.some + _type = `image/tiff`.some, + extensionFields = ().asJsonObject // "eo:bands": [3], ), "B5" -> StacItemAsset( @@ -259,7 +267,8 @@ class CatalogLayerSpec extends AnyFunSpec with Matchers { title = "NIR Band (B5)".some, description = "NIR Band Top Of the Atmosphere".some, roles = Nil, - _type = `image/tiff`.some + _type = `image/tiff`.some, + extensionFields = ().asJsonObject // "eo:bands": [4], ), "B6" -> StacItemAsset( @@ -267,7 +276,8 @@ class CatalogLayerSpec extends AnyFunSpec with Matchers { title = "SWIR (B6)".some, description = "SWIR Band Top Of the Atmosphere".some, roles = Nil, - _type = `image/tiff`.some + _type = `image/tiff`.some, + extensionFields = ().asJsonObject // "eo:bands": [5], ), "B7" -> StacItemAsset( @@ -275,7 +285,8 @@ class CatalogLayerSpec extends AnyFunSpec with Matchers { title = "SWIR Band (B7)".some, description = "SWIR Band Top Of the Atmosphere".some, roles = Nil, - _type = `image/tiff`.some + _type = `image/tiff`.some, + extensionFields = ().asJsonObject // "eo:bands": [6], ), "B8" -> StacItemAsset( @@ -283,7 +294,8 @@ class CatalogLayerSpec extends AnyFunSpec with Matchers { title = "Panchromatic Band (B8)".some, description = "Panchromatic Band Top Of the Atmosphere".some, roles = Nil, - _type = `image/tiff`.some + _type = `image/tiff`.some, + extensionFields = ().asJsonObject // "eo:bands": [7], ), "B9" -> StacItemAsset( @@ -291,7 +303,8 @@ class CatalogLayerSpec extends AnyFunSpec with Matchers { title = "Cirrus Band (B9)".some, description = "Cirrus Band Top Of the Atmosphere".some, roles = Nil, - _type = `image/tiff`.some + _type = `image/tiff`.some, + extensionFields = ().asJsonObject // "eo:bands": [8], ), "B10" -> StacItemAsset( @@ -299,7 +312,8 @@ class CatalogLayerSpec extends AnyFunSpec with Matchers { title = "LWIR Band (B10)".some, description = "LWIR Band Top Of the Atmosphere".some, roles = Nil, - _type = `image/tiff`.some + _type = `image/tiff`.some, + extensionFields = ().asJsonObject // "eo:bands": [9], ), "B11" -> StacItemAsset( @@ -307,7 +321,8 @@ class CatalogLayerSpec extends AnyFunSpec with Matchers { title = "LWIR Band (B11)".some, description = "LWIR Band Top Of the Atmosphere".some, roles = Nil, - _type = `image/tiff`.some + _type = `image/tiff`.some, + extensionFields = ().asJsonObject // "eo:bands": [10], ) ), diff --git a/modules/core/src/test/scala/com/azavea/stac4s/CatalogSpec.scala b/modules/core/src/test/scala/com/azavea/stac4s/CatalogSpec.scala index 1e88febd..866c459a 100644 --- a/modules/core/src/test/scala/com/azavea/stac4s/CatalogSpec.scala +++ b/modules/core/src/test/scala/com/azavea/stac4s/CatalogSpec.scala @@ -43,7 +43,8 @@ class CatalogSpec extends AnyFunSpec with Matchers { title = None, extensionFields = ().asJsonObject ) - ) + ), + extensionFields = ().asJsonObject ) root.asJson.deepDropNullValues shouldBe getJson("/catalogs/landsat-stac/catalog.json") @@ -74,6 +75,7 @@ class CatalogSpec extends AnyFunSpec with Matchers { List(TemporalExtent(Instant.parse("2013-06-01T00:56:49.001Z"), Instant.parse("2020-01-01T00:56:49.001Z"))) ) ), + summaries = ().asJsonObject, // properties can be anything // it is a part where extensions can be // at least EO, Label and potentially the layer extension @@ -195,7 +197,8 @@ class CatalogSpec extends AnyFunSpec with Matchers { title = None, extensionFields = ().asJsonObject ) - ) + ), + extensionFields = ().asJsonObject ) collection.asJson.deepDropNullValues shouldBe getJson("/catalogs/landsat-stac/landsat-8-l1/catalog.json") @@ -297,21 +300,24 @@ class CatalogSpec extends AnyFunSpec with Matchers { title = "Thumbnail".some, description = "A medium sized thumbnail".some, roles = List(StacAssetRole.Thumbnail), - _type = `image/jpeg`.some + _type = `image/jpeg`.some, + extensionFields = ().asJsonObject ), "metadata" -> StacItemAsset( href = "http://landsat-pds.s3.amazonaws.com/L8/153/025/LC81530252014153LGN00/LC81530252014153LGN00_MTL.txt", title = "Original Metadata".some, description = "The original MTL metadata file provided for each Landsat scene".some, roles = List(StacAssetRole.Metadata), - _type = VendorMediaType("mtl").some + _type = VendorMediaType("mtl").some, + extensionFields = ().asJsonObject ), "B1" -> StacItemAsset( href = "http://landsat-pds.s3.amazonaws.com/L8/153/025/LC81530252014153LGN00/LC81530252014153LGN00_B1.TIF", title = "Coastal Band (B1)".some, description = "Coastal Band Top Of the Atmosphere".some, roles = Nil, - _type = `image/tiff`.some + _type = `image/tiff`.some, + extensionFields = ().asJsonObject // "eo:bands": [0], ), "B2" -> StacItemAsset( @@ -319,7 +325,8 @@ class CatalogSpec extends AnyFunSpec with Matchers { title = "Blue Band (B2)".some, description = "Blue Band Top Of the Atmosphere".some, roles = Nil, - _type = `image/tiff`.some + _type = `image/tiff`.some, + extensionFields = ().asJsonObject // "eo:bands": [1], ), "B3" -> StacItemAsset( @@ -327,7 +334,8 @@ class CatalogSpec extends AnyFunSpec with Matchers { title = "Green Band (B3)".some, description = "Green Band Top Of the Atmosphere".some, roles = Nil, - _type = `image/tiff`.some + _type = `image/tiff`.some, + extensionFields = ().asJsonObject // "eo:bands": [2], ), "B4" -> StacItemAsset( @@ -335,7 +343,8 @@ class CatalogSpec extends AnyFunSpec with Matchers { title = "Red Band (B4)".some, description = "Red Band Top Of the Atmosphere".some, roles = Nil, - _type = `image/tiff`.some + _type = `image/tiff`.some, + extensionFields = ().asJsonObject // "eo:bands": [3], ), "B5" -> StacItemAsset( @@ -343,7 +352,8 @@ class CatalogSpec extends AnyFunSpec with Matchers { title = "NIR Band (B5)".some, description = "NIR Band Top Of the Atmosphere".some, roles = Nil, - _type = `image/tiff`.some + _type = `image/tiff`.some, + extensionFields = ().asJsonObject // "eo:bands": [4], ), "B6" -> StacItemAsset( @@ -351,7 +361,8 @@ class CatalogSpec extends AnyFunSpec with Matchers { title = "SWIR (B6)".some, description = "SWIR Band Top Of the Atmosphere".some, roles = Nil, - _type = `image/tiff`.some + _type = `image/tiff`.some, + extensionFields = ().asJsonObject // "eo:bands": [5], ), "B7" -> StacItemAsset( @@ -359,7 +370,8 @@ class CatalogSpec extends AnyFunSpec with Matchers { title = "SWIR Band (B7)".some, description = "SWIR Band Top Of the Atmosphere".some, roles = Nil, - _type = `image/tiff`.some + _type = `image/tiff`.some, + extensionFields = ().asJsonObject // "eo:bands": [6], ), "B8" -> StacItemAsset( @@ -367,7 +379,8 @@ class CatalogSpec extends AnyFunSpec with Matchers { title = "Panchromatic Band (B8)".some, description = "Panchromatic Band Top Of the Atmosphere".some, roles = Nil, - _type = `image/tiff`.some + _type = `image/tiff`.some, + extensionFields = ().asJsonObject // "eo:bands": [7], ), "B9" -> StacItemAsset( @@ -375,7 +388,8 @@ class CatalogSpec extends AnyFunSpec with Matchers { title = "Cirrus Band (B9)".some, description = "Cirrus Band Top Of the Atmosphere".some, roles = Nil, - _type = `image/tiff`.some + _type = `image/tiff`.some, + extensionFields = ().asJsonObject // "eo:bands": [8], ), "B10" -> StacItemAsset( @@ -383,7 +397,8 @@ class CatalogSpec extends AnyFunSpec with Matchers { title = "LWIR Band (B10)".some, description = "LWIR Band Top Of the Atmosphere".some, roles = Nil, - _type = `image/tiff`.some + _type = `image/tiff`.some, + extensionFields = ().asJsonObject // "eo:bands": [9], ), "B11" -> StacItemAsset( @@ -391,7 +406,8 @@ class CatalogSpec extends AnyFunSpec with Matchers { title = "LWIR Band (B11)".some, description = "LWIR Band Top Of the Atmosphere".some, roles = Nil, - _type = `image/tiff`.some + _type = `image/tiff`.some, + extensionFields = ().asJsonObject // "eo:bands": [10], ) ), diff --git a/modules/core/src/test/scala/com/azavea/stac4s/Generators.scala b/modules/core/src/test/scala/com/azavea/stac4s/Generators.scala index 149c9df7..bca5b296 100644 --- a/modules/core/src/test/scala/com/azavea/stac4s/Generators.scala +++ b/modules/core/src/test/scala/com/azavea/stac4s/Generators.scala @@ -165,7 +165,8 @@ object Generators { Gen.option(nonEmptyStringGen), Gen.option(nonEmptyStringGen), Gen.containerOf[Set, StacAssetRole](assetRoleGen) map { _.toList }, - Gen.option(mediaTypeGen) + Gen.option(mediaTypeGen), + Gen.const(().asJsonObject) ) mapN { StacItemAsset.apply } @@ -206,7 +207,8 @@ object Generators { nonEmptyStringGen, Gen.option(nonEmptyStringGen), nonEmptyStringGen, - Gen.listOf(stacLinkGen) + Gen.listOf(stacLinkGen), + Gen.const(().asJsonObject) ).mapN(StacCatalog.apply) private def stacCollectionGen: Gen[StacCollection] = @@ -220,8 +222,10 @@ object Generators { stacLicenseGen, Gen.listOf(stacProviderGen), stacExtentGen, + Gen.const(().asJsonObject), Gen.const(JsonObject.fromMap(Map.empty)), - Gen.listOf(stacLinkGen) + Gen.listOf(stacLinkGen), + Gen.const(().asJsonObject) ).mapN(StacCollection.apply) private def itemCollectionGen: Gen[ItemCollection] = From dba21607ca44bc03d059bb16e78dc61c9fa40eb1 Mon Sep 17 00:00:00 2001 From: James Santucci Date: Fri, 15 May 2020 09:23:09 -0500 Subject: [PATCH 08/26] fix collection decoder --- .../src/main/scala/com/azavea/stac4s/StacCollection.scala | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/core/src/main/scala/com/azavea/stac4s/StacCollection.scala b/modules/core/src/main/scala/com/azavea/stac4s/StacCollection.scala index b4bf0fe8..c8340357 100644 --- a/modules/core/src/main/scala/com/azavea/stac4s/StacCollection.scala +++ b/modules/core/src/main/scala/com/azavea/stac4s/StacCollection.scala @@ -122,7 +122,9 @@ object StacCollection { summaries getOrElse JsonObject.fromMap(Map.empty), properties, links, - extensionFields + extensionFields.filter({ + case (k, _) => !collectionFields.contains(k) + }) ) ) } From c97c2cb7689bc99ff81641bb74f545fdbd797505 Mon Sep 17 00:00:00 2001 From: James Santucci Date: Fri, 15 May 2020 09:23:15 -0500 Subject: [PATCH 09/26] update testing static json --- .../resources/catalogs/landsat-stac/landsat-8-l1/catalog.json | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/core/src/test/resources/catalogs/landsat-stac/landsat-8-l1/catalog.json b/modules/core/src/test/resources/catalogs/landsat-stac/landsat-8-l1/catalog.json index 7fb70822..2d80fbaa 100644 --- a/modules/core/src/test/resources/catalogs/landsat-stac/landsat-8-l1/catalog.json +++ b/modules/core/src/test/resources/catalogs/landsat-stac/landsat-8-l1/catalog.json @@ -43,6 +43,7 @@ ] } }, + "summaries": {}, "properties" : { "collection" : "landsat-8-l1", "eo:instrument" : "OLI_TIRS", From c842be5c94e8db08798e473236cd3cd1b902ea27 Mon Sep 17 00:00:00 2001 From: James Santucci Date: Fri, 15 May 2020 10:56:48 -0500 Subject: [PATCH 10/26] add itemcollection extension --- .../com/azavea/stac4s/ItemCollection.scala | 71 ++++++++++++++----- .../extensions/ItemCollectionExtension.scala | 29 ++++++++ .../scala/com/azavea/stac4s/Generators.scala | 3 +- 3 files changed, 83 insertions(+), 20 deletions(-) create mode 100644 modules/core/src/main/scala/com/azavea/stac4s/extensions/ItemCollectionExtension.scala diff --git a/modules/core/src/main/scala/com/azavea/stac4s/ItemCollection.scala b/modules/core/src/main/scala/com/azavea/stac4s/ItemCollection.scala index f36d7027..82b82718 100644 --- a/modules/core/src/main/scala/com/azavea/stac4s/ItemCollection.scala +++ b/modules/core/src/main/scala/com/azavea/stac4s/ItemCollection.scala @@ -1,42 +1,75 @@ package com.azavea.stac4s import cats.Eq +import cats.implicits._ import io.circe._ import io.circe.refined._ +import io.circe.syntax._ final case class ItemCollection( _type: String = "FeatureCollection", stacVersion: StacVersion, stacExtensions: List[String], features: List[StacItem], - links: List[StacLink] + links: List[StacLink], + extensionFields: JsonObject ) object ItemCollection { - implicit val eqItemCollection: Eq[ItemCollection] = Eq.fromUniversalEquals - - implicit val encItemCollection: Encoder[ItemCollection] = Encoder.forProduct5( + val itemCollectionFields = Set( "type", "stac_version", "stac_extensions", "features", "links" - )(itemCollection => - ( - itemCollection._type, - itemCollection.stacVersion, - itemCollection.stacExtensions, - itemCollection.features, - itemCollection.links - ) ) - implicit val decItemCollection: Decoder[ItemCollection] = Decoder.forProduct5( - "type", - "stac_version", - "stac_extensions", - "features", - "links" - )(ItemCollection.apply) + implicit val eqItemCollection: Eq[ItemCollection] = Eq.fromUniversalEquals + + implicit val encItemCollection: Encoder[ItemCollection] = new Encoder[ItemCollection] { + + def apply(collection: ItemCollection): Json = { + val baseEncoder: Encoder[ItemCollection] = Encoder.forProduct5( + "type", + "stac_version", + "stac_extensions", + "features", + "links" + )(itemCollection => + ( + itemCollection._type, + itemCollection.stacVersion, + itemCollection.stacExtensions, + itemCollection.features, + itemCollection.links + ) + ) + + baseEncoder(collection).deepMerge(collection.extensionFields.asJson) + } + } + + implicit val decItemCollection: Decoder[ItemCollection] = { c: HCursor => + ( + c.get[String]("type"), + c.get[StacVersion]("stac_version"), + c.get[Option[List[String]]]("stac_extensions"), + c.get[List[StacItem]]("features"), + c.get[Option[List[StacLink]]]("links"), + c.value.as[JsonObject] + ).mapN( + ( + _type: String, + stacVersion: StacVersion, + extensions: Option[List[String]], + features: List[StacItem], + links: Option[List[StacLink]], + document: JsonObject + ) => + ItemCollection(_type, stacVersion, extensions getOrElse Nil, features, links getOrElse Nil, document.filter({ + case (k, _) => !itemCollectionFields.contains(k) + })) + ) + } } diff --git a/modules/core/src/main/scala/com/azavea/stac4s/extensions/ItemCollectionExtension.scala b/modules/core/src/main/scala/com/azavea/stac4s/extensions/ItemCollectionExtension.scala new file mode 100644 index 00000000..c1162e7e --- /dev/null +++ b/modules/core/src/main/scala/com/azavea/stac4s/extensions/ItemCollectionExtension.scala @@ -0,0 +1,29 @@ +package com.azavea.stac4s.extensions + +import com.azavea.stac4s._ + +import io.circe.{Decoder, Encoder} +import io.circe.syntax._ + +trait ItemCollectionExtension[T] { + def getProperties(itemCollection: ItemCollection): ExtensionResult[T] + def extend(itemCollection: ItemCollection, properties: T): ItemCollection +} + +object ItemExtensionCollection { + def apply[T](implicit ev: ItemCollectionExtension[T]): ItemCollectionExtension[T] = ev + + def instance[T](implicit decoder: Decoder[T], objectEncoder: Encoder.AsObject[T]): ItemCollectionExtension[T] = + new ItemCollectionExtension[T] { + + def getProperties(itemCollection: ItemCollection): ExtensionResult[T] = + decoder.decodeAccumulating( + itemCollection.extensionFields.asJson.hcursor + ) + + def extend(itemCollection: ItemCollection, extensionProperties: T) = + itemCollection.copy(extensionFields = + itemCollection.extensionFields.deepMerge(objectEncoder.encodeObject(extensionProperties)) + ) + } +} diff --git a/modules/core/src/test/scala/com/azavea/stac4s/Generators.scala b/modules/core/src/test/scala/com/azavea/stac4s/Generators.scala index bca5b296..76e3d8b5 100644 --- a/modules/core/src/test/scala/com/azavea/stac4s/Generators.scala +++ b/modules/core/src/test/scala/com/azavea/stac4s/Generators.scala @@ -234,7 +234,8 @@ object Generators { Gen.const(StacVersion.unsafeFrom("0.9.0")), Gen.const(Nil), Gen.listOf[StacItem](stacItemGen), - Gen.listOf[StacLink](stacLinkGen) + Gen.listOf[StacLink](stacLinkGen), + Gen.const(().asJsonObject) ).mapN(ItemCollection.apply) private def labelClassNameGen: Gen[LabelClassName] = From 02b2e5bb8878b6f35860155b53deaa85c8452329 Mon Sep 17 00:00:00 2001 From: James Santucci Date: Fri, 15 May 2020 10:58:39 -0500 Subject: [PATCH 11/26] update pr template --- .github/PULL_REQUEST_TEMPLATE.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 136ec537..eaeb1de5 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -5,6 +5,7 @@ Brief description of what this PR does, and why it is needed. ### Checklist - [ ] New tests have been added or existing tests have been modified +- [ ] Changelog updated ### Notes From 304f2a26bfd640a3a9b6e9c6a7f0713e19364d02 Mon Sep 17 00:00:00 2001 From: James Santucci Date: Fri, 15 May 2020 11:10:01 -0500 Subject: [PATCH 12/26] Update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cc73fc12..b13e6620 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ### Added +- Created typeclasses for linking extensions to the items they extend [#85](https://github.com/azavea/stac4s/pull/85) + ### Changed ### Deprecated From 5b3aec80c9e1a22a078aef240fecdec2b8b9f244 Mon Sep 17 00:00:00 2001 From: James Santucci Date: Fri, 15 May 2020 11:41:00 -0500 Subject: [PATCH 13/26] Move asset extension type to extensions --- .../scala/com/azavea/stac4s/StacCatalog.scala | 11 +++++++- .../com/azavea/stac4s/StacItemAsset.scala | 16 +++++++++--- .../scala/com/azavea/stac4s/StacLink.scala | 4 ++- .../extensions/CollectionExtension.scala | 26 +++++++++++++++++++ ...tension.scala => ItemAssetExtension.scala} | 8 +++--- .../stac4s/extensions/LinkExtension.scala | 4 ++- .../asset/AssetCollectionExtension.scala | 23 ++++++++++++++++ .../asset}/StacCollectionAsset.scala | 4 ++- 8 files changed, 84 insertions(+), 12 deletions(-) create mode 100644 modules/core/src/main/scala/com/azavea/stac4s/extensions/CollectionExtension.scala rename modules/core/src/main/scala/com/azavea/stac4s/extensions/{AssetExtension.scala => ItemAssetExtension.scala} (80%) create mode 100644 modules/core/src/main/scala/com/azavea/stac4s/extensions/asset/AssetCollectionExtension.scala rename modules/core/src/main/scala/com/azavea/stac4s/{ => extensions/asset}/StacCollectionAsset.scala (90%) diff --git a/modules/core/src/main/scala/com/azavea/stac4s/StacCatalog.scala b/modules/core/src/main/scala/com/azavea/stac4s/StacCatalog.scala index fbbb3309..f3c2e688 100644 --- a/modules/core/src/main/scala/com/azavea/stac4s/StacCatalog.scala +++ b/modules/core/src/main/scala/com/azavea/stac4s/StacCatalog.scala @@ -17,6 +17,15 @@ final case class StacCatalog( object StacCatalog { + val catalogFields = Set( + "stac_version", + "stac_extensions", + "id", + "title", + "description", + "links" + ) + implicit val eqStacCatalog: Eq[StacCatalog] = Eq.fromUniversalEquals implicit val encCatalog: Encoder[StacCatalog] = new Encoder[StacCatalog] { @@ -65,7 +74,7 @@ object StacCatalog { description, links, document.filter({ - case (k, _) => !Set("stac_version", "stac_extensions", "id", "title", "description", "links").contains(k) + case (k, _) => !catalogFields.contains(k) }) ) ) diff --git a/modules/core/src/main/scala/com/azavea/stac4s/StacItemAsset.scala b/modules/core/src/main/scala/com/azavea/stac4s/StacItemAsset.scala index bcef6422..b355659c 100644 --- a/modules/core/src/main/scala/com/azavea/stac4s/StacItemAsset.scala +++ b/modules/core/src/main/scala/com/azavea/stac4s/StacItemAsset.scala @@ -16,6 +16,14 @@ final case class StacItemAsset( object StacItemAsset { + val assetFields = Set( + "href", + "title", + "description", + "roles", + "type" + ) + implicit val eqStacItemAsset: Eq[StacItemAsset] = Eq.fromUniversalEquals implicit val encStacItemAsset: Encoder[StacItemAsset] = new Encoder[StacItemAsset] { @@ -35,7 +43,7 @@ object StacItemAsset { c.get[String]("href"), c.get[Option[String]]("title"), c.get[Option[String]]("description"), - c.get[List[StacAssetRole]]("roles"), + c.get[Option[List[StacAssetRole]]]("roles"), c.get[Option[StacMediaType]]("type"), c.value.as[JsonObject] ).mapN( @@ -43,12 +51,12 @@ object StacItemAsset { href: String, title: Option[String], description: Option[String], - roles: List[StacAssetRole], + roles: Option[List[StacAssetRole]], mediaType: Option[StacMediaType], document: JsonObject ) => - StacItemAsset(href, title, description, roles, mediaType, document.filter({ - case (k, _) => !Set("href", "title", "description", "roles", "type").contains(k) + StacItemAsset(href, title, description, roles getOrElse Nil, mediaType, document.filter({ + case (k, _) => !assetFields.contains(k) })) ) } diff --git a/modules/core/src/main/scala/com/azavea/stac4s/StacLink.scala b/modules/core/src/main/scala/com/azavea/stac4s/StacLink.scala index 588f8737..afd612db 100644 --- a/modules/core/src/main/scala/com/azavea/stac4s/StacLink.scala +++ b/modules/core/src/main/scala/com/azavea/stac4s/StacLink.scala @@ -14,6 +14,8 @@ final case class StacLink( object StacLink { + val linkFields = Set("href", "rel", "type", "title") + implicit val encStacLink: Encoder[StacLink] = new Encoder[StacLink] { def apply(link: StacLink): Json = { @@ -45,7 +47,7 @@ object StacLink { document: JsonObject ) => StacLink(href, rel, _type, title, document.filter({ - case (k, _) => !Set("href", "rel", "type", "title").contains(k) + case (k, _) => !linkFields.contains(k) })) ) } diff --git a/modules/core/src/main/scala/com/azavea/stac4s/extensions/CollectionExtension.scala b/modules/core/src/main/scala/com/azavea/stac4s/extensions/CollectionExtension.scala new file mode 100644 index 00000000..237352cb --- /dev/null +++ b/modules/core/src/main/scala/com/azavea/stac4s/extensions/CollectionExtension.scala @@ -0,0 +1,26 @@ +package com.azavea.stac4s.extensions + +import com.azavea.stac4s.StacCollection + +import io.circe.{Decoder, Encoder} +import io.circe.syntax._ + +trait CollectionExtension[T] { + def getProperties(collection: StacCollection): ExtensionResult[T] + + def extend(collection: StacCollection, extensionFields: T): StacCollection +} + +object CollectionExtension { + def apply[T](implicit ev: CollectionExtension[T]): CollectionExtension[T] = ev + + def instance[T](implicit decoder: Decoder[T], objectEncoder: Encoder.AsObject[T]) = + new CollectionExtension[T] { + + def getProperties(collection: StacCollection): ExtensionResult[T] = + decoder.decodeAccumulating(collection.extensionFields.asJson.hcursor) + + def extend(collection: StacCollection, extensionFields: T): StacCollection = + collection.copy(extensionFields = collection.extensionFields.deepMerge(objectEncoder.encodeObject(extensionFields))) + } +} diff --git a/modules/core/src/main/scala/com/azavea/stac4s/extensions/AssetExtension.scala b/modules/core/src/main/scala/com/azavea/stac4s/extensions/ItemAssetExtension.scala similarity index 80% rename from modules/core/src/main/scala/com/azavea/stac4s/extensions/AssetExtension.scala rename to modules/core/src/main/scala/com/azavea/stac4s/extensions/ItemAssetExtension.scala index 38f85879..df57e0dc 100644 --- a/modules/core/src/main/scala/com/azavea/stac4s/extensions/AssetExtension.scala +++ b/modules/core/src/main/scala/com/azavea/stac4s/extensions/ItemAssetExtension.scala @@ -5,16 +5,16 @@ import com.azavea.stac4s.StacItemAsset import io.circe.{Decoder, Encoder} import io.circe.syntax._ -trait AssetExtension[T] { +trait ItemAssetExtension[T] { def getProperties(asset: StacItemAsset): ExtensionResult[T] def extend(asset: StacItemAsset, extensionFields: T): StacItemAsset } -object AssetExtension { - def apply[T](implicit ev: AssetExtension[T]): AssetExtension[T] = ev +object ItemAssetExtension { + def apply[T](implicit ev: ItemAssetExtension[T]): ItemAssetExtension[T] = ev def instance[T](implicit decoder: Decoder[T], objectEncoder: Encoder.AsObject[T]) = - new AssetExtension[T] { + new ItemAssetExtension[T] { def getProperties(asset: StacItemAsset): ExtensionResult[T] = decoder.decodeAccumulating(asset.extensionFields.asJson.hcursor) diff --git a/modules/core/src/main/scala/com/azavea/stac4s/extensions/LinkExtension.scala b/modules/core/src/main/scala/com/azavea/stac4s/extensions/LinkExtension.scala index 3d35c626..e1d7991d 100644 --- a/modules/core/src/main/scala/com/azavea/stac4s/extensions/LinkExtension.scala +++ b/modules/core/src/main/scala/com/azavea/stac4s/extensions/LinkExtension.scala @@ -21,6 +21,8 @@ object LinkExtension { decoder.decodeAccumulating(link.extensionFields.asJson.hcursor) def extend(link: StacLink, extensionFields: T): StacLink = - link.copy(extensionFields = link.extensionFields.deepMerge(objectEncoder.encodeObject(extensionFields))) + link.copy(extensionFields = + link.extensionFields.deepMerge(objectEncoder.encodeObject(extensionFields)) + ) } } diff --git a/modules/core/src/main/scala/com/azavea/stac4s/extensions/asset/AssetCollectionExtension.scala b/modules/core/src/main/scala/com/azavea/stac4s/extensions/asset/AssetCollectionExtension.scala new file mode 100644 index 00000000..458570e1 --- /dev/null +++ b/modules/core/src/main/scala/com/azavea/stac4s/extensions/asset/AssetCollectionExtension.scala @@ -0,0 +1,23 @@ +package com.azavea.stac4s.extensions.asset + +import com.azavea.stac4s.extensions.CollectionExtension + +import io.circe._ +import io.circe.syntax._ + +final case class AssetCollectionExtension( + assets: Map[String, StacCollectionAsset] +) + +object AssetCollectionExtension { + + implicit val encAssetCollectionExtension: Encoder.AsObject[AssetCollectionExtension] = Encoder + .AsObject[Map[String, Json]] + .contramapObject((extensionFields: AssetCollectionExtension) => Map("assets" -> extensionFields.assets.asJson)) + + implicit val decAssetCollectionExtension: Decoder[AssetCollectionExtension] = + Decoder.forProduct1("assets")(AssetCollectionExtension.apply) + + implicit val collectionExtension: CollectionExtension[AssetCollectionExtension] = + CollectionExtension.instance +} diff --git a/modules/core/src/main/scala/com/azavea/stac4s/StacCollectionAsset.scala b/modules/core/src/main/scala/com/azavea/stac4s/extensions/asset/StacCollectionAsset.scala similarity index 90% rename from modules/core/src/main/scala/com/azavea/stac4s/StacCollectionAsset.scala rename to modules/core/src/main/scala/com/azavea/stac4s/extensions/asset/StacCollectionAsset.scala index 0dfe4256..71a26712 100644 --- a/modules/core/src/main/scala/com/azavea/stac4s/StacCollectionAsset.scala +++ b/modules/core/src/main/scala/com/azavea/stac4s/extensions/asset/StacCollectionAsset.scala @@ -1,4 +1,6 @@ -package com.azavea.stac4s +package com.azavea.stac4s.extensions.asset + +import com.azavea.stac4s._ import cats.Eq import io.circe._ From 3f9cfb465ae8263d887392b3fe9a1ce3d9eaa5aa Mon Sep 17 00:00:00 2001 From: James Santucci Date: Fri, 15 May 2020 11:58:31 -0500 Subject: [PATCH 14/26] test asset extension --- .../test/scala/com/azavea/stac4s/Generators.scala | 13 ++++++++++++- .../test/scala/com/azavea/stac4s/SerDeSpec.scala | 1 + 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/modules/core/src/test/scala/com/azavea/stac4s/Generators.scala b/modules/core/src/test/scala/com/azavea/stac4s/Generators.scala index 76e3d8b5..14fafeef 100644 --- a/modules/core/src/test/scala/com/azavea/stac4s/Generators.scala +++ b/modules/core/src/test/scala/com/azavea/stac4s/Generators.scala @@ -1,6 +1,7 @@ package com.azavea.stac4s import com.azavea.stac4s.extensions.label._ +import com.azavea.stac4s.extensions.asset._ import cats.data.NonEmptyList import cats.implicits._ import geotrellis.vector.{Geometry, Point, Polygon} @@ -37,6 +38,16 @@ object Generators { private def instantGen: Gen[Instant] = arbitrary[Int] map { x => Instant.now.plusMillis(x.toLong) } + private def collectionExtensionFieldsGen: Gen[JsonObject] = Gen.oneOf( + Gen.const(().asJsonObject), + Gen + .mapOf( + (nonEmptyStringGen, stacCollectionAssetGen).tupled + ) + .map(AssetCollectionExtension.apply) + .map(_.asJsonObject) + ) + private def linkExtensionFields: Gen[JsonObject] = Gen.oneOf( Gen.const(().asJsonObject), Gen @@ -225,7 +236,7 @@ object Generators { Gen.const(().asJsonObject), Gen.const(JsonObject.fromMap(Map.empty)), Gen.listOf(stacLinkGen), - Gen.const(().asJsonObject) + collectionExtensionFieldsGen ).mapN(StacCollection.apply) private def itemCollectionGen: Gen[ItemCollection] = diff --git a/modules/core/src/test/scala/com/azavea/stac4s/SerDeSpec.scala b/modules/core/src/test/scala/com/azavea/stac4s/SerDeSpec.scala index d778a322..0e956d1b 100644 --- a/modules/core/src/test/scala/com/azavea/stac4s/SerDeSpec.scala +++ b/modules/core/src/test/scala/com/azavea/stac4s/SerDeSpec.scala @@ -1,5 +1,6 @@ package com.azavea.stac4s +import com.azavea.stac4s.extensions.asset._ import com.azavea.stac4s.extensions.label._ import com.azavea.stac4s.meta._ import Generators._ From d95ba5e44fffbde33e7f56b2a9565447bc0a83f1 Mon Sep 17 00:00:00 2001 From: James Santucci Date: Fri, 15 May 2020 12:21:35 -0500 Subject: [PATCH 15/26] Update comment --- .../main/scala/com/azavea/stac4s/extensions/ItemExtension.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/core/src/main/scala/com/azavea/stac4s/extensions/ItemExtension.scala b/modules/core/src/main/scala/com/azavea/stac4s/extensions/ItemExtension.scala index 56ded08b..b39f6d3a 100644 --- a/modules/core/src/main/scala/com/azavea/stac4s/extensions/ItemExtension.scala +++ b/modules/core/src/main/scala/com/azavea/stac4s/extensions/ItemExtension.scala @@ -15,7 +15,7 @@ object ItemExtension { // summoner def apply[T](implicit ev: ItemExtension[T]): ItemExtension[T] = ev - // constructor for anything with a `Decoder` + // constructor for anything with a `Decoder` and an `Encoder.AsObject` def instance[T](implicit decoder: Decoder[T], objectEncoder: Encoder.AsObject[T]): ItemExtension[T] = new ItemExtension[T] { From 5c1d81f810650ec9b162b41422cb32a6a6d2fcb1 Mon Sep 17 00:00:00 2001 From: James Santucci Date: Mon, 18 May 2020 17:13:00 -0500 Subject: [PATCH 16/26] layer ids string list -> non empty string list --- .../azavea/stac4s/extensions/layer/LayerItemExtension.scala | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/core/src/main/scala/com/azavea/stac4s/extensions/layer/LayerItemExtension.scala b/modules/core/src/main/scala/com/azavea/stac4s/extensions/layer/LayerItemExtension.scala index 0b3cbaa8..7bd19b79 100644 --- a/modules/core/src/main/scala/com/azavea/stac4s/extensions/layer/LayerItemExtension.scala +++ b/modules/core/src/main/scala/com/azavea/stac4s/extensions/layer/LayerItemExtension.scala @@ -3,10 +3,12 @@ package com.azavea.stac4s.extensions.layer import com.azavea.stac4s.extensions.ItemExtension import cats.Eq +import eu.timepit.refined.types.string.NonEmptyString import io.circe.{Decoder, Encoder} +import io.circe.refined._ import io.circe.syntax._ -case class LayerItemExtension(ids: List[String]) +case class LayerItemExtension(ids: List[NonEmptyString]) object LayerItemExtension { implicit val eqLayerProperties: Eq[LayerItemExtension] = Eq.fromUniversalEquals From 2e89e74fd4dcee05940050ac3bd1917b306ab575 Mon Sep 17 00:00:00 2001 From: James Santucci Date: Mon, 18 May 2020 17:20:55 -0500 Subject: [PATCH 17/26] nonemptystrings and sets oh my --- .../com/azavea/stac4s/StacItemAsset.scala | 8 ++--- .../extensions/layer/LayerItemExtension.scala | 2 +- .../com/azavea/stac4s/CatalogLayerSpec.scala | 35 ++++++++++--------- .../scala/com/azavea/stac4s/CatalogSpec.scala | 26 +++++++------- .../scala/com/azavea/stac4s/Generators.scala | 5 +-- 5 files changed, 40 insertions(+), 36 deletions(-) diff --git a/modules/core/src/main/scala/com/azavea/stac4s/StacItemAsset.scala b/modules/core/src/main/scala/com/azavea/stac4s/StacItemAsset.scala index b355659c..6d771ca4 100644 --- a/modules/core/src/main/scala/com/azavea/stac4s/StacItemAsset.scala +++ b/modules/core/src/main/scala/com/azavea/stac4s/StacItemAsset.scala @@ -9,7 +9,7 @@ final case class StacItemAsset( href: String, title: Option[String], description: Option[String], - roles: List[StacAssetRole], + roles: Set[StacAssetRole], _type: Option[StacMediaType], extensionFields: JsonObject ) @@ -43,7 +43,7 @@ object StacItemAsset { c.get[String]("href"), c.get[Option[String]]("title"), c.get[Option[String]]("description"), - c.get[Option[List[StacAssetRole]]]("roles"), + c.get[Option[Set[StacAssetRole]]]("roles"), c.get[Option[StacMediaType]]("type"), c.value.as[JsonObject] ).mapN( @@ -51,11 +51,11 @@ object StacItemAsset { href: String, title: Option[String], description: Option[String], - roles: Option[List[StacAssetRole]], + roles: Option[Set[StacAssetRole]], mediaType: Option[StacMediaType], document: JsonObject ) => - StacItemAsset(href, title, description, roles getOrElse Nil, mediaType, document.filter({ + StacItemAsset(href, title, description, roles getOrElse Set.empty, mediaType, document.filter({ case (k, _) => !assetFields.contains(k) })) ) diff --git a/modules/core/src/main/scala/com/azavea/stac4s/extensions/layer/LayerItemExtension.scala b/modules/core/src/main/scala/com/azavea/stac4s/extensions/layer/LayerItemExtension.scala index 7bd19b79..c7ae2bfd 100644 --- a/modules/core/src/main/scala/com/azavea/stac4s/extensions/layer/LayerItemExtension.scala +++ b/modules/core/src/main/scala/com/azavea/stac4s/extensions/layer/LayerItemExtension.scala @@ -16,7 +16,7 @@ object LayerItemExtension { implicit val encLayerProperties: Encoder.AsObject[LayerItemExtension] = Encoder.AsObject.instance[LayerItemExtension] { o => Map("layer:ids" -> o.ids.asJson).asJsonObject } - implicit val decLayerProperties: Decoder[LayerItemExtension] = Decoder[Map[String, List[String]]] emap { + implicit val decLayerProperties: Decoder[LayerItemExtension] = Decoder[Map[String, List[NonEmptyString]]] emap { _.get("layer:ids") match { case Some(l) => Right(LayerItemExtension(l)) case _ => Left("Could not decode LayerProperties.") diff --git a/modules/core/src/test/scala/com/azavea/stac4s/CatalogLayerSpec.scala b/modules/core/src/test/scala/com/azavea/stac4s/CatalogLayerSpec.scala index a8726ef0..37c70352 100644 --- a/modules/core/src/test/scala/com/azavea/stac4s/CatalogLayerSpec.scala +++ b/modules/core/src/test/scala/com/azavea/stac4s/CatalogLayerSpec.scala @@ -1,10 +1,11 @@ package com.azavea.stac4s import com.azavea.stac4s.extensions.layer._ -import io.circe.syntax._ import cats.syntax.either._ import cats.syntax.option._ -import geotrellis.vector._ +import eu.timepit.refined.types.string.NonEmptyString +import geotrellis.vector.{io => _, _} +import io.circe.syntax._ import org.scalatest.funspec.AnyFunSpec import org.scalatest.matchers.should.Matchers @@ -176,7 +177,9 @@ class CatalogLayerSpec extends AnyFunSpec with Matchers { "landsat:geometric_rmse_model_y" -> 4.654.asJson, "landsat:geometric_rmse_verify" -> 5.364.asJson, "landsat:image_quality_oli" -> 9.asJson - ).asJsonObject.deepMerge(LayerItemExtension(List(layerUS.id, layerCA.id)).asJsonObject), // layer extension + ).asJsonObject.deepMerge( + LayerItemExtension(List(layerUS.id, layerCA.id) map { NonEmptyString.unsafeFrom }).asJsonObject + ), // layer extension links = List( StacLink( href = "../../catalog.json", @@ -214,7 +217,7 @@ class CatalogLayerSpec extends AnyFunSpec with Matchers { "http://landsat-pds.s3.amazonaws.com/L8/153/025/LC81530252014153LGN00/LC81530252014153LGN00_thumb_large.jpg", title = "Thumbnail".some, description = "A medium sized thumbnail".some, - roles = List(StacAssetRole.Thumbnail), + roles = Set(StacAssetRole.Thumbnail), _type = `image/jpeg`.some, extensionFields = ().asJsonObject ), @@ -222,7 +225,7 @@ class CatalogLayerSpec extends AnyFunSpec with Matchers { href = "http://landsat-pds.s3.amazonaws.com/L8/153/025/LC81530252014153LGN00/LC81530252014153LGN00_MTL.txt", title = "Original Metadata".some, description = "The original MTL metadata file provided for each Landsat scene".some, - roles = List(StacAssetRole.Metadata), + roles = Set(StacAssetRole.Metadata), _type = VendorMediaType("mtl").some, extensionFields = ().asJsonObject ), @@ -230,7 +233,7 @@ class CatalogLayerSpec extends AnyFunSpec with Matchers { href = "http://landsat-pds.s3.amazonaws.com/L8/153/025/LC81530252014153LGN00/LC81530252014153LGN00_B1.TIF", title = "Coastal Band (B1)".some, description = "Coastal Band Top Of the Atmosphere".some, - roles = Nil, + roles = Set.empty, _type = `image/tiff`.some, extensionFields = ().asJsonObject // "eo:bands": [0], @@ -239,7 +242,7 @@ class CatalogLayerSpec extends AnyFunSpec with Matchers { href = "http://landsat-pds.s3.amazonaws.com/L8/153/025/LC81530252014153LGN00/LC81530252014153LGN00_B2.TIF", title = "Blue Band (B2)".some, description = "Blue Band Top Of the Atmosphere".some, - roles = Nil, + roles = Set.empty, _type = `image/tiff`.some, extensionFields = ().asJsonObject // "eo:bands": [1], @@ -248,7 +251,7 @@ class CatalogLayerSpec extends AnyFunSpec with Matchers { href = "http://landsat-pds.s3.amazonaws.com/L8/153/025/LC81530252014153LGN00/LC81530252014153LGN00_B3.TIF", title = "Green Band (B3)".some, description = "Green Band Top Of the Atmosphere".some, - roles = Nil, + roles = Set.empty, _type = `image/tiff`.some, extensionFields = ().asJsonObject // "eo:bands": [2], @@ -257,7 +260,7 @@ class CatalogLayerSpec extends AnyFunSpec with Matchers { href = "http://landsat-pds.s3.amazonaws.com/L8/153/025/LC81530252014153LGN00/LC81530252014153LGN00_B4.TIF", title = "Red Band (B4)".some, description = "Red Band Top Of the Atmosphere".some, - roles = Nil, + roles = Set.empty, _type = `image/tiff`.some, extensionFields = ().asJsonObject // "eo:bands": [3], @@ -266,7 +269,7 @@ class CatalogLayerSpec extends AnyFunSpec with Matchers { href = "http://landsat-pds.s3.amazonaws.com/L8/153/025/LC81530252014153LGN00/LC81530252014153LGN00_B5.TIF", title = "NIR Band (B5)".some, description = "NIR Band Top Of the Atmosphere".some, - roles = Nil, + roles = Set.empty, _type = `image/tiff`.some, extensionFields = ().asJsonObject // "eo:bands": [4], @@ -275,7 +278,7 @@ class CatalogLayerSpec extends AnyFunSpec with Matchers { href = "http://landsat-pds.s3.amazonaws.com/L8/153/025/LC81530252014153LGN00/LC81530252014153LGN00_B6.TIF", title = "SWIR (B6)".some, description = "SWIR Band Top Of the Atmosphere".some, - roles = Nil, + roles = Set.empty, _type = `image/tiff`.some, extensionFields = ().asJsonObject // "eo:bands": [5], @@ -284,7 +287,7 @@ class CatalogLayerSpec extends AnyFunSpec with Matchers { href = "http://landsat-pds.s3.amazonaws.com/L8/153/025/LC81530252014153LGN00/LC81530252014153LGN00_B7.TIF", title = "SWIR Band (B7)".some, description = "SWIR Band Top Of the Atmosphere".some, - roles = Nil, + roles = Set.empty, _type = `image/tiff`.some, extensionFields = ().asJsonObject // "eo:bands": [6], @@ -293,7 +296,7 @@ class CatalogLayerSpec extends AnyFunSpec with Matchers { href = "http://landsat-pds.s3.amazonaws.com/L8/153/025/LC81530252014153LGN00/LC81530252014153LGN00_B8.TIF", title = "Panchromatic Band (B8)".some, description = "Panchromatic Band Top Of the Atmosphere".some, - roles = Nil, + roles = Set.empty, _type = `image/tiff`.some, extensionFields = ().asJsonObject // "eo:bands": [7], @@ -302,7 +305,7 @@ class CatalogLayerSpec extends AnyFunSpec with Matchers { href = "http://landsat-pds.s3.amazonaws.com/L8/153/025/LC81530252014153LGN00/LC81530252014153LGN00_B9.TIF", title = "Cirrus Band (B9)".some, description = "Cirrus Band Top Of the Atmosphere".some, - roles = Nil, + roles = Set.empty, _type = `image/tiff`.some, extensionFields = ().asJsonObject // "eo:bands": [8], @@ -311,7 +314,7 @@ class CatalogLayerSpec extends AnyFunSpec with Matchers { href = "http://landsat-pds.s3.amazonaws.com/L8/153/025/LC81530252014153LGN00/LC81530252014153LGN00_B10.TIF", title = "LWIR Band (B10)".some, description = "LWIR Band Top Of the Atmosphere".some, - roles = Nil, + roles = Set.empty, _type = `image/tiff`.some, extensionFields = ().asJsonObject // "eo:bands": [9], @@ -320,7 +323,7 @@ class CatalogLayerSpec extends AnyFunSpec with Matchers { href = "http://landsat-pds.s3.amazonaws.com/L8/153/025/LC81530252014153LGN00/LC81530252014153LGN00_B11.TIF", title = "LWIR Band (B11)".some, description = "LWIR Band Top Of the Atmosphere".some, - roles = Nil, + roles = Set.empty, _type = `image/tiff`.some, extensionFields = ().asJsonObject // "eo:bands": [10], diff --git a/modules/core/src/test/scala/com/azavea/stac4s/CatalogSpec.scala b/modules/core/src/test/scala/com/azavea/stac4s/CatalogSpec.scala index 866c459a..4b01e502 100644 --- a/modules/core/src/test/scala/com/azavea/stac4s/CatalogSpec.scala +++ b/modules/core/src/test/scala/com/azavea/stac4s/CatalogSpec.scala @@ -299,7 +299,7 @@ class CatalogSpec extends AnyFunSpec with Matchers { "http://landsat-pds.s3.amazonaws.com/L8/153/025/LC81530252014153LGN00/LC81530252014153LGN00_thumb_large.jpg", title = "Thumbnail".some, description = "A medium sized thumbnail".some, - roles = List(StacAssetRole.Thumbnail), + roles = Set(StacAssetRole.Thumbnail), _type = `image/jpeg`.some, extensionFields = ().asJsonObject ), @@ -307,7 +307,7 @@ class CatalogSpec extends AnyFunSpec with Matchers { href = "http://landsat-pds.s3.amazonaws.com/L8/153/025/LC81530252014153LGN00/LC81530252014153LGN00_MTL.txt", title = "Original Metadata".some, description = "The original MTL metadata file provided for each Landsat scene".some, - roles = List(StacAssetRole.Metadata), + roles = Set(StacAssetRole.Metadata), _type = VendorMediaType("mtl").some, extensionFields = ().asJsonObject ), @@ -315,7 +315,7 @@ class CatalogSpec extends AnyFunSpec with Matchers { href = "http://landsat-pds.s3.amazonaws.com/L8/153/025/LC81530252014153LGN00/LC81530252014153LGN00_B1.TIF", title = "Coastal Band (B1)".some, description = "Coastal Band Top Of the Atmosphere".some, - roles = Nil, + roles = Set.empty, _type = `image/tiff`.some, extensionFields = ().asJsonObject // "eo:bands": [0], @@ -324,7 +324,7 @@ class CatalogSpec extends AnyFunSpec with Matchers { href = "http://landsat-pds.s3.amazonaws.com/L8/153/025/LC81530252014153LGN00/LC81530252014153LGN00_B2.TIF", title = "Blue Band (B2)".some, description = "Blue Band Top Of the Atmosphere".some, - roles = Nil, + roles = Set.empty, _type = `image/tiff`.some, extensionFields = ().asJsonObject // "eo:bands": [1], @@ -333,7 +333,7 @@ class CatalogSpec extends AnyFunSpec with Matchers { href = "http://landsat-pds.s3.amazonaws.com/L8/153/025/LC81530252014153LGN00/LC81530252014153LGN00_B3.TIF", title = "Green Band (B3)".some, description = "Green Band Top Of the Atmosphere".some, - roles = Nil, + roles = Set.empty, _type = `image/tiff`.some, extensionFields = ().asJsonObject // "eo:bands": [2], @@ -342,7 +342,7 @@ class CatalogSpec extends AnyFunSpec with Matchers { href = "http://landsat-pds.s3.amazonaws.com/L8/153/025/LC81530252014153LGN00/LC81530252014153LGN00_B4.TIF", title = "Red Band (B4)".some, description = "Red Band Top Of the Atmosphere".some, - roles = Nil, + roles = Set.empty, _type = `image/tiff`.some, extensionFields = ().asJsonObject // "eo:bands": [3], @@ -351,7 +351,7 @@ class CatalogSpec extends AnyFunSpec with Matchers { href = "http://landsat-pds.s3.amazonaws.com/L8/153/025/LC81530252014153LGN00/LC81530252014153LGN00_B5.TIF", title = "NIR Band (B5)".some, description = "NIR Band Top Of the Atmosphere".some, - roles = Nil, + roles = Set.empty, _type = `image/tiff`.some, extensionFields = ().asJsonObject // "eo:bands": [4], @@ -360,7 +360,7 @@ class CatalogSpec extends AnyFunSpec with Matchers { href = "http://landsat-pds.s3.amazonaws.com/L8/153/025/LC81530252014153LGN00/LC81530252014153LGN00_B6.TIF", title = "SWIR (B6)".some, description = "SWIR Band Top Of the Atmosphere".some, - roles = Nil, + roles = Set.empty, _type = `image/tiff`.some, extensionFields = ().asJsonObject // "eo:bands": [5], @@ -369,7 +369,7 @@ class CatalogSpec extends AnyFunSpec with Matchers { href = "http://landsat-pds.s3.amazonaws.com/L8/153/025/LC81530252014153LGN00/LC81530252014153LGN00_B7.TIF", title = "SWIR Band (B7)".some, description = "SWIR Band Top Of the Atmosphere".some, - roles = Nil, + roles = Set.empty, _type = `image/tiff`.some, extensionFields = ().asJsonObject // "eo:bands": [6], @@ -378,7 +378,7 @@ class CatalogSpec extends AnyFunSpec with Matchers { href = "http://landsat-pds.s3.amazonaws.com/L8/153/025/LC81530252014153LGN00/LC81530252014153LGN00_B8.TIF", title = "Panchromatic Band (B8)".some, description = "Panchromatic Band Top Of the Atmosphere".some, - roles = Nil, + roles = Set.empty, _type = `image/tiff`.some, extensionFields = ().asJsonObject // "eo:bands": [7], @@ -387,7 +387,7 @@ class CatalogSpec extends AnyFunSpec with Matchers { href = "http://landsat-pds.s3.amazonaws.com/L8/153/025/LC81530252014153LGN00/LC81530252014153LGN00_B9.TIF", title = "Cirrus Band (B9)".some, description = "Cirrus Band Top Of the Atmosphere".some, - roles = Nil, + roles = Set.empty, _type = `image/tiff`.some, extensionFields = ().asJsonObject // "eo:bands": [8], @@ -396,7 +396,7 @@ class CatalogSpec extends AnyFunSpec with Matchers { href = "http://landsat-pds.s3.amazonaws.com/L8/153/025/LC81530252014153LGN00/LC81530252014153LGN00_B10.TIF", title = "LWIR Band (B10)".some, description = "LWIR Band Top Of the Atmosphere".some, - roles = Nil, + roles = Set.empty, _type = `image/tiff`.some, extensionFields = ().asJsonObject // "eo:bands": [9], @@ -405,7 +405,7 @@ class CatalogSpec extends AnyFunSpec with Matchers { href = "http://landsat-pds.s3.amazonaws.com/L8/153/025/LC81530252014153LGN00/LC81530252014153LGN00_B11.TIF", title = "LWIR Band (B11)".some, description = "LWIR Band Top Of the Atmosphere".some, - roles = Nil, + roles = Set.empty, _type = `image/tiff`.some, extensionFields = ().asJsonObject // "eo:bands": [10], diff --git a/modules/core/src/test/scala/com/azavea/stac4s/Generators.scala b/modules/core/src/test/scala/com/azavea/stac4s/Generators.scala index 14fafeef..6c6827a2 100644 --- a/modules/core/src/test/scala/com/azavea/stac4s/Generators.scala +++ b/modules/core/src/test/scala/com/azavea/stac4s/Generators.scala @@ -16,6 +16,7 @@ import com.github.tbouron.SpdxLicense import com.azavea.stac4s.extensions.label.LabelClassClasses.NamedLabelClasses import com.azavea.stac4s.extensions.label.LabelClassClasses.NumberedLabelClasses import com.azavea.stac4s.extensions.layer.LayerItemExtension +import eu.timepit.refined.types.string.NonEmptyString object Generators { @@ -175,7 +176,7 @@ object Generators { nonEmptyStringGen, Gen.option(nonEmptyStringGen), Gen.option(nonEmptyStringGen), - Gen.containerOf[Set, StacAssetRole](assetRoleGen) map { _.toList }, + Gen.containerOf[Set, StacAssetRole](assetRoleGen), Gen.option(mediaTypeGen), Gen.const(().asJsonObject) ) mapN { @@ -331,7 +332,7 @@ object Generators { Gen.option(Gen.listOf(nonEmptyStringGen)).map(LabelProperties.fromOption) private def layerPropertiesGen: Gen[LayerItemExtension] = - Gen.listOf(nonEmptyStringGen).map(LayerItemExtension.apply) + Gen.listOf(nonEmptyStringGen).map(layerIds => LayerItemExtension(layerIds map { NonEmptyString.unsafeFrom })) private def labelExtensionPropertiesGen: Gen[LabelItemExtension] = ( From 9a349f555cfd24fdfa2b6f980335ab10d7e6ff61 Mon Sep 17 00:00:00 2001 From: James Santucci Date: Mon, 18 May 2020 17:36:19 -0500 Subject: [PATCH 18/26] layer extension requires nonemptylist --- .../azavea/stac4s/extensions/layer/LayerItemExtension.scala | 5 +++-- .../src/test/scala/com/azavea/stac4s/CatalogLayerSpec.scala | 6 +++--- .../core/src/test/scala/com/azavea/stac4s/Generators.scala | 4 +++- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/modules/core/src/main/scala/com/azavea/stac4s/extensions/layer/LayerItemExtension.scala b/modules/core/src/main/scala/com/azavea/stac4s/extensions/layer/LayerItemExtension.scala index c7ae2bfd..7f387c42 100644 --- a/modules/core/src/main/scala/com/azavea/stac4s/extensions/layer/LayerItemExtension.scala +++ b/modules/core/src/main/scala/com/azavea/stac4s/extensions/layer/LayerItemExtension.scala @@ -2,13 +2,14 @@ package com.azavea.stac4s.extensions.layer import com.azavea.stac4s.extensions.ItemExtension +import cats.data.NonEmptyList import cats.Eq import eu.timepit.refined.types.string.NonEmptyString import io.circe.{Decoder, Encoder} import io.circe.refined._ import io.circe.syntax._ -case class LayerItemExtension(ids: List[NonEmptyString]) +case class LayerItemExtension(ids: NonEmptyList[NonEmptyString]) object LayerItemExtension { implicit val eqLayerProperties: Eq[LayerItemExtension] = Eq.fromUniversalEquals @@ -16,7 +17,7 @@ object LayerItemExtension { implicit val encLayerProperties: Encoder.AsObject[LayerItemExtension] = Encoder.AsObject.instance[LayerItemExtension] { o => Map("layer:ids" -> o.ids.asJson).asJsonObject } - implicit val decLayerProperties: Decoder[LayerItemExtension] = Decoder[Map[String, List[NonEmptyString]]] emap { + implicit val decLayerProperties: Decoder[LayerItemExtension] = Decoder[Map[String, NonEmptyList[NonEmptyString]]] emap { _.get("layer:ids") match { case Some(l) => Right(LayerItemExtension(l)) case _ => Left("Could not decode LayerProperties.") diff --git a/modules/core/src/test/scala/com/azavea/stac4s/CatalogLayerSpec.scala b/modules/core/src/test/scala/com/azavea/stac4s/CatalogLayerSpec.scala index 37c70352..350b51fd 100644 --- a/modules/core/src/test/scala/com/azavea/stac4s/CatalogLayerSpec.scala +++ b/modules/core/src/test/scala/com/azavea/stac4s/CatalogLayerSpec.scala @@ -1,8 +1,8 @@ package com.azavea.stac4s import com.azavea.stac4s.extensions.layer._ -import cats.syntax.either._ -import cats.syntax.option._ +import cats.data.NonEmptyList +import cats.implicits._ import eu.timepit.refined.types.string.NonEmptyString import geotrellis.vector.{io => _, _} import io.circe.syntax._ @@ -178,7 +178,7 @@ class CatalogLayerSpec extends AnyFunSpec with Matchers { "landsat:geometric_rmse_verify" -> 5.364.asJson, "landsat:image_quality_oli" -> 9.asJson ).asJsonObject.deepMerge( - LayerItemExtension(List(layerUS.id, layerCA.id) map { NonEmptyString.unsafeFrom }).asJsonObject + LayerItemExtension(NonEmptyList.of(layerUS.id, layerCA.id) map { NonEmptyString.unsafeFrom }).asJsonObject ), // layer extension links = List( StacLink( diff --git a/modules/core/src/test/scala/com/azavea/stac4s/Generators.scala b/modules/core/src/test/scala/com/azavea/stac4s/Generators.scala index 6c6827a2..f2cf4b2f 100644 --- a/modules/core/src/test/scala/com/azavea/stac4s/Generators.scala +++ b/modules/core/src/test/scala/com/azavea/stac4s/Generators.scala @@ -332,7 +332,9 @@ object Generators { Gen.option(Gen.listOf(nonEmptyStringGen)).map(LabelProperties.fromOption) private def layerPropertiesGen: Gen[LayerItemExtension] = - Gen.listOf(nonEmptyStringGen).map(layerIds => LayerItemExtension(layerIds map { NonEmptyString.unsafeFrom })) + Gen + .nonEmptyListOf(nonEmptyStringGen) + .map(layerIds => LayerItemExtension(NonEmptyList.fromListUnsafe(layerIds map { NonEmptyString.unsafeFrom }))) private def labelExtensionPropertiesGen: Gen[LabelItemExtension] = ( From aa8a265aae5fbce10a4d793c6e3c49557472fe14 Mon Sep 17 00:00:00 2001 From: James Santucci Date: Tue, 19 May 2020 08:33:44 -0500 Subject: [PATCH 19/26] provide default for extensionFields --- .../com/azavea/stac4s/ItemCollection.scala | 2 +- .../scala/com/azavea/stac4s/StacCatalog.scala | 2 +- .../com/azavea/stac4s/StacCollection.scala | 2 +- .../com/azavea/stac4s/StacItemAsset.scala | 2 +- .../scala/com/azavea/stac4s/StacLink.scala | 2 +- .../com/azavea/stac4s/CatalogLayerSpec.scala | 84 +++++++------------ .../scala/com/azavea/stac4s/CatalogSpec.scala | 78 ++++++----------- 7 files changed, 59 insertions(+), 113 deletions(-) diff --git a/modules/core/src/main/scala/com/azavea/stac4s/ItemCollection.scala b/modules/core/src/main/scala/com/azavea/stac4s/ItemCollection.scala index 82b82718..4d4732ce 100644 --- a/modules/core/src/main/scala/com/azavea/stac4s/ItemCollection.scala +++ b/modules/core/src/main/scala/com/azavea/stac4s/ItemCollection.scala @@ -12,7 +12,7 @@ final case class ItemCollection( stacExtensions: List[String], features: List[StacItem], links: List[StacLink], - extensionFields: JsonObject + extensionFields: JsonObject = ().asJsonObject ) object ItemCollection { diff --git a/modules/core/src/main/scala/com/azavea/stac4s/StacCatalog.scala b/modules/core/src/main/scala/com/azavea/stac4s/StacCatalog.scala index f3c2e688..e77681b1 100644 --- a/modules/core/src/main/scala/com/azavea/stac4s/StacCatalog.scala +++ b/modules/core/src/main/scala/com/azavea/stac4s/StacCatalog.scala @@ -12,7 +12,7 @@ final case class StacCatalog( title: Option[String], description: String, links: List[StacLink], - extensionFields: JsonObject + extensionFields: JsonObject = ().asJsonObject ) object StacCatalog { diff --git a/modules/core/src/main/scala/com/azavea/stac4s/StacCollection.scala b/modules/core/src/main/scala/com/azavea/stac4s/StacCollection.scala index c8340357..7373a996 100644 --- a/modules/core/src/main/scala/com/azavea/stac4s/StacCollection.scala +++ b/modules/core/src/main/scala/com/azavea/stac4s/StacCollection.scala @@ -19,7 +19,7 @@ final case class StacCollection( summaries: JsonObject, properties: JsonObject, links: List[StacLink], - extensionFields: JsonObject + extensionFields: JsonObject = ().asJsonObject ) object StacCollection { diff --git a/modules/core/src/main/scala/com/azavea/stac4s/StacItemAsset.scala b/modules/core/src/main/scala/com/azavea/stac4s/StacItemAsset.scala index 6d771ca4..0650cdd2 100644 --- a/modules/core/src/main/scala/com/azavea/stac4s/StacItemAsset.scala +++ b/modules/core/src/main/scala/com/azavea/stac4s/StacItemAsset.scala @@ -11,7 +11,7 @@ final case class StacItemAsset( description: Option[String], roles: Set[StacAssetRole], _type: Option[StacMediaType], - extensionFields: JsonObject + extensionFields: JsonObject = ().asJsonObject ) object StacItemAsset { diff --git a/modules/core/src/main/scala/com/azavea/stac4s/StacLink.scala b/modules/core/src/main/scala/com/azavea/stac4s/StacLink.scala index afd612db..55a22d59 100644 --- a/modules/core/src/main/scala/com/azavea/stac4s/StacLink.scala +++ b/modules/core/src/main/scala/com/azavea/stac4s/StacLink.scala @@ -9,7 +9,7 @@ final case class StacLink( rel: StacLinkType, _type: Option[StacMediaType], title: Option[String], - extensionFields: JsonObject + extensionFields: JsonObject = ().asJsonObject ) object StacLink { diff --git a/modules/core/src/test/scala/com/azavea/stac4s/CatalogLayerSpec.scala b/modules/core/src/test/scala/com/azavea/stac4s/CatalogLayerSpec.scala index 350b51fd..fb1413e5 100644 --- a/modules/core/src/test/scala/com/azavea/stac4s/CatalogLayerSpec.scala +++ b/modules/core/src/test/scala/com/azavea/stac4s/CatalogLayerSpec.scala @@ -27,39 +27,33 @@ class CatalogLayerSpec extends AnyFunSpec with Matchers { href = "./catalog.json", rel = StacLinkType.Self, _type = None, - title = None, - extensionFields = ().asJsonObject + title = None ), StacLink( href = "./catalog.json", rel = StacLinkType.StacRoot, _type = None, - title = None, - extensionFields = ().asJsonObject + title = None ), StacLink( href = "./landsat-8-l1/catalog.json", rel = StacLinkType.Child, _type = None, - title = None, - extensionFields = ().asJsonObject + title = None ), StacLink( href = "./layers/ca/catalog.json", rel = StacLinkType.Child, _type = None, - title = None, - extensionFields = ().asJsonObject + title = None ), StacLink( href = "./layers/us/catalog.json", rel = StacLinkType.Child, _type = None, - title = None, - extensionFields = ().asJsonObject + title = None ) - ), - extensionFields = ().asJsonObject + ) ) root.asJson.deepDropNullValues shouldBe getJson("/catalogs/landsat-stac-layers/catalog.json") @@ -78,32 +72,27 @@ class CatalogLayerSpec extends AnyFunSpec with Matchers { href = "../catalog.json", rel = StacLinkType.StacRoot, _type = None, - title = None, - extensionFields = ().asJsonObject + title = None ), StacLink( href = "../catalog.json", rel = StacLinkType.Parent, _type = None, - title = None, - extensionFields = ().asJsonObject + title = None ), StacLink( href = "./catalog.json", rel = StacLinkType.Self, _type = None, - title = None, - extensionFields = ().asJsonObject + title = None ), StacLink( href = "../../landsat-8-l1/2014-153/LC81530252014153LGN00.json", rel = StacLinkType.Item, _type = None, - title = None, - extensionFields = ().asJsonObject + title = None ) - ), - extensionFields = ().asJsonObject + ) ) val layerCA = layerUS.copy( @@ -185,30 +174,26 @@ class CatalogLayerSpec extends AnyFunSpec with Matchers { href = "../../catalog.json", rel = StacLinkType.StacRoot, _type = None, - title = None, - extensionFields = ().asJsonObject + title = None ), StacLink( href = "../catalog.json", rel = StacLinkType.Parent, _type = None, - title = None, - extensionFields = ().asJsonObject + title = None ), StacLink( href = "./LC81530252014153LGN00.json", rel = StacLinkType.Self, _type = None, - title = None, - extensionFields = ().asJsonObject + title = None ), // { "rel":"alternate", "href": "https://landsatonaws.com/L8/153/025/LC81530252014153LGN00", "type": "text/html"}, StacLink( href = "https://landsatonaws.com/L8/153/025/LC81530252014153LGN0", rel = StacLinkType.Alternate, _type = `text/html`.some, - title = None, - extensionFields = ().asJsonObject + title = None ) ), assets = Map( @@ -218,24 +203,21 @@ class CatalogLayerSpec extends AnyFunSpec with Matchers { title = "Thumbnail".some, description = "A medium sized thumbnail".some, roles = Set(StacAssetRole.Thumbnail), - _type = `image/jpeg`.some, - extensionFields = ().asJsonObject + _type = `image/jpeg`.some ), "metadata" -> StacItemAsset( href = "http://landsat-pds.s3.amazonaws.com/L8/153/025/LC81530252014153LGN00/LC81530252014153LGN00_MTL.txt", title = "Original Metadata".some, description = "The original MTL metadata file provided for each Landsat scene".some, roles = Set(StacAssetRole.Metadata), - _type = VendorMediaType("mtl").some, - extensionFields = ().asJsonObject + _type = VendorMediaType("mtl").some ), "B1" -> StacItemAsset( href = "http://landsat-pds.s3.amazonaws.com/L8/153/025/LC81530252014153LGN00/LC81530252014153LGN00_B1.TIF", title = "Coastal Band (B1)".some, description = "Coastal Band Top Of the Atmosphere".some, roles = Set.empty, - _type = `image/tiff`.some, - extensionFields = ().asJsonObject + _type = `image/tiff`.some // "eo:bands": [0], ), "B2" -> StacItemAsset( @@ -243,8 +225,7 @@ class CatalogLayerSpec extends AnyFunSpec with Matchers { title = "Blue Band (B2)".some, description = "Blue Band Top Of the Atmosphere".some, roles = Set.empty, - _type = `image/tiff`.some, - extensionFields = ().asJsonObject + _type = `image/tiff`.some // "eo:bands": [1], ), "B3" -> StacItemAsset( @@ -252,8 +233,7 @@ class CatalogLayerSpec extends AnyFunSpec with Matchers { title = "Green Band (B3)".some, description = "Green Band Top Of the Atmosphere".some, roles = Set.empty, - _type = `image/tiff`.some, - extensionFields = ().asJsonObject + _type = `image/tiff`.some // "eo:bands": [2], ), "B4" -> StacItemAsset( @@ -261,8 +241,7 @@ class CatalogLayerSpec extends AnyFunSpec with Matchers { title = "Red Band (B4)".some, description = "Red Band Top Of the Atmosphere".some, roles = Set.empty, - _type = `image/tiff`.some, - extensionFields = ().asJsonObject + _type = `image/tiff`.some // "eo:bands": [3], ), "B5" -> StacItemAsset( @@ -270,8 +249,7 @@ class CatalogLayerSpec extends AnyFunSpec with Matchers { title = "NIR Band (B5)".some, description = "NIR Band Top Of the Atmosphere".some, roles = Set.empty, - _type = `image/tiff`.some, - extensionFields = ().asJsonObject + _type = `image/tiff`.some // "eo:bands": [4], ), "B6" -> StacItemAsset( @@ -279,8 +257,7 @@ class CatalogLayerSpec extends AnyFunSpec with Matchers { title = "SWIR (B6)".some, description = "SWIR Band Top Of the Atmosphere".some, roles = Set.empty, - _type = `image/tiff`.some, - extensionFields = ().asJsonObject + _type = `image/tiff`.some // "eo:bands": [5], ), "B7" -> StacItemAsset( @@ -288,8 +265,7 @@ class CatalogLayerSpec extends AnyFunSpec with Matchers { title = "SWIR Band (B7)".some, description = "SWIR Band Top Of the Atmosphere".some, roles = Set.empty, - _type = `image/tiff`.some, - extensionFields = ().asJsonObject + _type = `image/tiff`.some // "eo:bands": [6], ), "B8" -> StacItemAsset( @@ -297,8 +273,7 @@ class CatalogLayerSpec extends AnyFunSpec with Matchers { title = "Panchromatic Band (B8)".some, description = "Panchromatic Band Top Of the Atmosphere".some, roles = Set.empty, - _type = `image/tiff`.some, - extensionFields = ().asJsonObject + _type = `image/tiff`.some // "eo:bands": [7], ), "B9" -> StacItemAsset( @@ -306,8 +281,7 @@ class CatalogLayerSpec extends AnyFunSpec with Matchers { title = "Cirrus Band (B9)".some, description = "Cirrus Band Top Of the Atmosphere".some, roles = Set.empty, - _type = `image/tiff`.some, - extensionFields = ().asJsonObject + _type = `image/tiff`.some // "eo:bands": [8], ), "B10" -> StacItemAsset( @@ -315,8 +289,7 @@ class CatalogLayerSpec extends AnyFunSpec with Matchers { title = "LWIR Band (B10)".some, description = "LWIR Band Top Of the Atmosphere".some, roles = Set.empty, - _type = `image/tiff`.some, - extensionFields = ().asJsonObject + _type = `image/tiff`.some // "eo:bands": [9], ), "B11" -> StacItemAsset( @@ -324,8 +297,7 @@ class CatalogLayerSpec extends AnyFunSpec with Matchers { title = "LWIR Band (B11)".some, description = "LWIR Band Top Of the Atmosphere".some, roles = Set.empty, - _type = `image/tiff`.some, - extensionFields = ().asJsonObject + _type = `image/tiff`.some // "eo:bands": [10], ) ), diff --git a/modules/core/src/test/scala/com/azavea/stac4s/CatalogSpec.scala b/modules/core/src/test/scala/com/azavea/stac4s/CatalogSpec.scala index 4b01e502..3c37e4c3 100644 --- a/modules/core/src/test/scala/com/azavea/stac4s/CatalogSpec.scala +++ b/modules/core/src/test/scala/com/azavea/stac4s/CatalogSpec.scala @@ -26,25 +26,21 @@ class CatalogSpec extends AnyFunSpec with Matchers { href = "./catalog.json", rel = StacLinkType.Self, _type = None, - title = None, - extensionFields = ().asJsonObject + title = None ), StacLink( href = "./catalog.json", rel = StacLinkType.StacRoot, _type = None, - title = None, - extensionFields = ().asJsonObject + title = None ), StacLink( href = "./landsat-8-l1/catalog.json", rel = StacLinkType.Child, _type = None, - title = None, - extensionFields = ().asJsonObject + title = None ) - ), - extensionFields = ().asJsonObject + ) ) root.asJson.deepDropNullValues shouldBe getJson("/catalogs/landsat-stac/catalog.json") @@ -173,32 +169,27 @@ class CatalogSpec extends AnyFunSpec with Matchers { href = "../../catalog.json", rel = StacLinkType.StacRoot, _type = None, - title = None, - extensionFields = ().asJsonObject + title = None ), StacLink( href = "../../catalog.json", rel = StacLinkType.Parent, _type = None, - title = None, - extensionFields = ().asJsonObject + title = None ), StacLink( href = "./catalog.json", rel = StacLinkType.Self, _type = None, - title = None, - extensionFields = ().asJsonObject + title = None ), StacLink( href = "./2014-153/LC81530252014153LGN00.json", rel = StacLinkType.Item, _type = None, - title = None, - extensionFields = ().asJsonObject + title = None ) - ), - extensionFields = ().asJsonObject + ) ) collection.asJson.deepDropNullValues shouldBe getJson("/catalogs/landsat-stac/landsat-8-l1/catalog.json") @@ -267,30 +258,26 @@ class CatalogSpec extends AnyFunSpec with Matchers { href = "../../catalog.json", rel = StacLinkType.StacRoot, _type = None, - title = None, - extensionFields = ().asJsonObject + title = None ), StacLink( href = "../catalog.json", rel = StacLinkType.Parent, _type = None, - title = None, - extensionFields = ().asJsonObject + title = None ), StacLink( href = "./LC81530252014153LGN00.json", rel = StacLinkType.Self, _type = None, - title = None, - extensionFields = ().asJsonObject + title = None ), // { "rel":"alternate", "href": "https://landsatonaws.com/L8/153/025/LC81530252014153LGN00", "type": "text/html"}, StacLink( href = "https://landsatonaws.com/L8/153/025/LC81530252014153LGN0", rel = StacLinkType.Alternate, _type = `text/html`.some, - title = None, - extensionFields = ().asJsonObject + title = None ) ), assets = Map( @@ -300,24 +287,21 @@ class CatalogSpec extends AnyFunSpec with Matchers { title = "Thumbnail".some, description = "A medium sized thumbnail".some, roles = Set(StacAssetRole.Thumbnail), - _type = `image/jpeg`.some, - extensionFields = ().asJsonObject + _type = `image/jpeg`.some ), "metadata" -> StacItemAsset( href = "http://landsat-pds.s3.amazonaws.com/L8/153/025/LC81530252014153LGN00/LC81530252014153LGN00_MTL.txt", title = "Original Metadata".some, description = "The original MTL metadata file provided for each Landsat scene".some, roles = Set(StacAssetRole.Metadata), - _type = VendorMediaType("mtl").some, - extensionFields = ().asJsonObject + _type = VendorMediaType("mtl").some ), "B1" -> StacItemAsset( href = "http://landsat-pds.s3.amazonaws.com/L8/153/025/LC81530252014153LGN00/LC81530252014153LGN00_B1.TIF", title = "Coastal Band (B1)".some, description = "Coastal Band Top Of the Atmosphere".some, roles = Set.empty, - _type = `image/tiff`.some, - extensionFields = ().asJsonObject + _type = `image/tiff`.some // "eo:bands": [0], ), "B2" -> StacItemAsset( @@ -325,8 +309,7 @@ class CatalogSpec extends AnyFunSpec with Matchers { title = "Blue Band (B2)".some, description = "Blue Band Top Of the Atmosphere".some, roles = Set.empty, - _type = `image/tiff`.some, - extensionFields = ().asJsonObject + _type = `image/tiff`.some // "eo:bands": [1], ), "B3" -> StacItemAsset( @@ -334,8 +317,7 @@ class CatalogSpec extends AnyFunSpec with Matchers { title = "Green Band (B3)".some, description = "Green Band Top Of the Atmosphere".some, roles = Set.empty, - _type = `image/tiff`.some, - extensionFields = ().asJsonObject + _type = `image/tiff`.some // "eo:bands": [2], ), "B4" -> StacItemAsset( @@ -343,8 +325,7 @@ class CatalogSpec extends AnyFunSpec with Matchers { title = "Red Band (B4)".some, description = "Red Band Top Of the Atmosphere".some, roles = Set.empty, - _type = `image/tiff`.some, - extensionFields = ().asJsonObject + _type = `image/tiff`.some // "eo:bands": [3], ), "B5" -> StacItemAsset( @@ -352,8 +333,7 @@ class CatalogSpec extends AnyFunSpec with Matchers { title = "NIR Band (B5)".some, description = "NIR Band Top Of the Atmosphere".some, roles = Set.empty, - _type = `image/tiff`.some, - extensionFields = ().asJsonObject + _type = `image/tiff`.some // "eo:bands": [4], ), "B6" -> StacItemAsset( @@ -361,8 +341,7 @@ class CatalogSpec extends AnyFunSpec with Matchers { title = "SWIR (B6)".some, description = "SWIR Band Top Of the Atmosphere".some, roles = Set.empty, - _type = `image/tiff`.some, - extensionFields = ().asJsonObject + _type = `image/tiff`.some // "eo:bands": [5], ), "B7" -> StacItemAsset( @@ -370,8 +349,7 @@ class CatalogSpec extends AnyFunSpec with Matchers { title = "SWIR Band (B7)".some, description = "SWIR Band Top Of the Atmosphere".some, roles = Set.empty, - _type = `image/tiff`.some, - extensionFields = ().asJsonObject + _type = `image/tiff`.some // "eo:bands": [6], ), "B8" -> StacItemAsset( @@ -379,8 +357,7 @@ class CatalogSpec extends AnyFunSpec with Matchers { title = "Panchromatic Band (B8)".some, description = "Panchromatic Band Top Of the Atmosphere".some, roles = Set.empty, - _type = `image/tiff`.some, - extensionFields = ().asJsonObject + _type = `image/tiff`.some // "eo:bands": [7], ), "B9" -> StacItemAsset( @@ -388,8 +365,7 @@ class CatalogSpec extends AnyFunSpec with Matchers { title = "Cirrus Band (B9)".some, description = "Cirrus Band Top Of the Atmosphere".some, roles = Set.empty, - _type = `image/tiff`.some, - extensionFields = ().asJsonObject + _type = `image/tiff`.some // "eo:bands": [8], ), "B10" -> StacItemAsset( @@ -397,8 +373,7 @@ class CatalogSpec extends AnyFunSpec with Matchers { title = "LWIR Band (B10)".some, description = "LWIR Band Top Of the Atmosphere".some, roles = Set.empty, - _type = `image/tiff`.some, - extensionFields = ().asJsonObject + _type = `image/tiff`.some // "eo:bands": [9], ), "B11" -> StacItemAsset( @@ -406,8 +381,7 @@ class CatalogSpec extends AnyFunSpec with Matchers { title = "LWIR Band (B11)".some, description = "LWIR Band Top Of the Atmosphere".some, roles = Set.empty, - _type = `image/tiff`.some, - extensionFields = ().asJsonObject + _type = `image/tiff`.some // "eo:bands": [10], ) ), From 75a67f9f8fb0376cd63c1435c5035dc7f45b9e93 Mon Sep 17 00:00:00 2001 From: James Santucci Date: Tue, 19 May 2020 08:40:16 -0500 Subject: [PATCH 20/26] s/getProperties/getExtensionFields --- .../com/azavea/stac4s/extensions/CatalogExtension.scala | 4 ++-- .../com/azavea/stac4s/extensions/CollectionExtension.scala | 4 ++-- .../com/azavea/stac4s/extensions/ItemAssetExtension.scala | 4 ++-- .../azavea/stac4s/extensions/ItemCollectionExtension.scala | 6 +++--- .../scala/com/azavea/stac4s/extensions/ItemExtension.scala | 4 ++-- .../scala/com/azavea/stac4s/extensions/LinkExtension.scala | 4 ++-- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/modules/core/src/main/scala/com/azavea/stac4s/extensions/CatalogExtension.scala b/modules/core/src/main/scala/com/azavea/stac4s/extensions/CatalogExtension.scala index 9dea4a7b..45d1c4b5 100644 --- a/modules/core/src/main/scala/com/azavea/stac4s/extensions/CatalogExtension.scala +++ b/modules/core/src/main/scala/com/azavea/stac4s/extensions/CatalogExtension.scala @@ -6,7 +6,7 @@ import io.circe.{Decoder, Encoder} import io.circe.syntax._ trait CatalogExtension[T] { - def getProperties(catalog: StacCatalog): ExtensionResult[T] + def getExtensionFields(catalog: StacCatalog): ExtensionResult[T] def extend(catalog: StacCatalog, extensionFields: T): StacCatalog } @@ -16,7 +16,7 @@ object CatalogExtension { def instance[T](implicit decoder: Decoder[T], objectEncoder: Encoder.AsObject[T]) = new CatalogExtension[T] { - def getProperties(catalog: StacCatalog): ExtensionResult[T] = + def getExtensionFields(catalog: StacCatalog): ExtensionResult[T] = decoder.decodeAccumulating(catalog.extensionFields.asJson.hcursor) def extend(catalog: StacCatalog, extensionFields: T): StacCatalog = diff --git a/modules/core/src/main/scala/com/azavea/stac4s/extensions/CollectionExtension.scala b/modules/core/src/main/scala/com/azavea/stac4s/extensions/CollectionExtension.scala index 237352cb..23c69d05 100644 --- a/modules/core/src/main/scala/com/azavea/stac4s/extensions/CollectionExtension.scala +++ b/modules/core/src/main/scala/com/azavea/stac4s/extensions/CollectionExtension.scala @@ -6,7 +6,7 @@ import io.circe.{Decoder, Encoder} import io.circe.syntax._ trait CollectionExtension[T] { - def getProperties(collection: StacCollection): ExtensionResult[T] + def getExtensionFields(collection: StacCollection): ExtensionResult[T] def extend(collection: StacCollection, extensionFields: T): StacCollection } @@ -17,7 +17,7 @@ object CollectionExtension { def instance[T](implicit decoder: Decoder[T], objectEncoder: Encoder.AsObject[T]) = new CollectionExtension[T] { - def getProperties(collection: StacCollection): ExtensionResult[T] = + def getExtensionFields(collection: StacCollection): ExtensionResult[T] = decoder.decodeAccumulating(collection.extensionFields.asJson.hcursor) def extend(collection: StacCollection, extensionFields: T): StacCollection = diff --git a/modules/core/src/main/scala/com/azavea/stac4s/extensions/ItemAssetExtension.scala b/modules/core/src/main/scala/com/azavea/stac4s/extensions/ItemAssetExtension.scala index df57e0dc..b994f08c 100644 --- a/modules/core/src/main/scala/com/azavea/stac4s/extensions/ItemAssetExtension.scala +++ b/modules/core/src/main/scala/com/azavea/stac4s/extensions/ItemAssetExtension.scala @@ -6,7 +6,7 @@ import io.circe.{Decoder, Encoder} import io.circe.syntax._ trait ItemAssetExtension[T] { - def getProperties(asset: StacItemAsset): ExtensionResult[T] + def getExtensionFields(asset: StacItemAsset): ExtensionResult[T] def extend(asset: StacItemAsset, extensionFields: T): StacItemAsset } @@ -16,7 +16,7 @@ object ItemAssetExtension { def instance[T](implicit decoder: Decoder[T], objectEncoder: Encoder.AsObject[T]) = new ItemAssetExtension[T] { - def getProperties(asset: StacItemAsset): ExtensionResult[T] = + def getExtensionFields(asset: StacItemAsset): ExtensionResult[T] = decoder.decodeAccumulating(asset.extensionFields.asJson.hcursor) def extend(asset: StacItemAsset, extensionFields: T): StacItemAsset = diff --git a/modules/core/src/main/scala/com/azavea/stac4s/extensions/ItemCollectionExtension.scala b/modules/core/src/main/scala/com/azavea/stac4s/extensions/ItemCollectionExtension.scala index c1162e7e..bfa32390 100644 --- a/modules/core/src/main/scala/com/azavea/stac4s/extensions/ItemCollectionExtension.scala +++ b/modules/core/src/main/scala/com/azavea/stac4s/extensions/ItemCollectionExtension.scala @@ -6,7 +6,7 @@ import io.circe.{Decoder, Encoder} import io.circe.syntax._ trait ItemCollectionExtension[T] { - def getProperties(itemCollection: ItemCollection): ExtensionResult[T] + def getExtensionFields(itemCollection: ItemCollection): ExtensionResult[T] def extend(itemCollection: ItemCollection, properties: T): ItemCollection } @@ -16,12 +16,12 @@ object ItemExtensionCollection { def instance[T](implicit decoder: Decoder[T], objectEncoder: Encoder.AsObject[T]): ItemCollectionExtension[T] = new ItemCollectionExtension[T] { - def getProperties(itemCollection: ItemCollection): ExtensionResult[T] = + def getExtensionFields(itemCollection: ItemCollection): ExtensionResult[T] = decoder.decodeAccumulating( itemCollection.extensionFields.asJson.hcursor ) - def extend(itemCollection: ItemCollection, extensionProperties: T) = + def extend(itemCollection: ItemCollection, extensionProperties: T): ItemCollection = itemCollection.copy(extensionFields = itemCollection.extensionFields.deepMerge(objectEncoder.encodeObject(extensionProperties)) ) diff --git a/modules/core/src/main/scala/com/azavea/stac4s/extensions/ItemExtension.scala b/modules/core/src/main/scala/com/azavea/stac4s/extensions/ItemExtension.scala index b39f6d3a..e58ec5a7 100644 --- a/modules/core/src/main/scala/com/azavea/stac4s/extensions/ItemExtension.scala +++ b/modules/core/src/main/scala/com/azavea/stac4s/extensions/ItemExtension.scala @@ -7,7 +7,7 @@ import io.circe.syntax._ // typeclass trait for anything that is an extension of item properties trait ItemExtension[T] { - def getProperties(item: StacItem): ExtensionResult[T] + def getExtensionFields(item: StacItem): ExtensionResult[T] def extend(item: StacItem, properties: T): StacItem } @@ -19,7 +19,7 @@ object ItemExtension { def instance[T](implicit decoder: Decoder[T], objectEncoder: Encoder.AsObject[T]): ItemExtension[T] = new ItemExtension[T] { - def getProperties(item: StacItem): ExtensionResult[T] = + def getExtensionFields(item: StacItem): ExtensionResult[T] = decoder.decodeAccumulating( item.properties.asJson.hcursor ) diff --git a/modules/core/src/main/scala/com/azavea/stac4s/extensions/LinkExtension.scala b/modules/core/src/main/scala/com/azavea/stac4s/extensions/LinkExtension.scala index e1d7991d..16aabe9a 100644 --- a/modules/core/src/main/scala/com/azavea/stac4s/extensions/LinkExtension.scala +++ b/modules/core/src/main/scala/com/azavea/stac4s/extensions/LinkExtension.scala @@ -6,7 +6,7 @@ import io.circe.{Decoder, Encoder} import io.circe.syntax._ trait LinkExtension[T] { - def getProperties(link: StacLink): ExtensionResult[T] + def getExtensionFields(link: StacLink): ExtensionResult[T] def extend(link: StacLink, extensionFields: T): StacLink } @@ -17,7 +17,7 @@ object LinkExtension { def instance[T](implicit decoder: Decoder[T], objectEncoder: Encoder.AsObject[T]) = new LinkExtension[T] { - def getProperties(link: StacLink): ExtensionResult[T] = + def getExtensionFields(link: StacLink): ExtensionResult[T] = decoder.decodeAccumulating(link.extensionFields.asJson.hcursor) def extend(link: StacLink, extensionFields: T): StacLink = From 680f39965befaa4efa1a91901869ce70245fe4e3 Mon Sep 17 00:00:00 2001 From: James Santucci Date: Tue, 19 May 2020 08:42:18 -0500 Subject: [PATCH 21/26] s/extend/addExtensionFields --- .../scala/com/azavea/stac4s/extensions/CatalogExtension.scala | 4 ++-- .../com/azavea/stac4s/extensions/CollectionExtension.scala | 4 ++-- .../com/azavea/stac4s/extensions/ItemAssetExtension.scala | 4 ++-- .../azavea/stac4s/extensions/ItemCollectionExtension.scala | 4 ++-- .../scala/com/azavea/stac4s/extensions/ItemExtension.scala | 4 ++-- .../scala/com/azavea/stac4s/extensions/LinkExtension.scala | 4 ++-- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/modules/core/src/main/scala/com/azavea/stac4s/extensions/CatalogExtension.scala b/modules/core/src/main/scala/com/azavea/stac4s/extensions/CatalogExtension.scala index 45d1c4b5..88047971 100644 --- a/modules/core/src/main/scala/com/azavea/stac4s/extensions/CatalogExtension.scala +++ b/modules/core/src/main/scala/com/azavea/stac4s/extensions/CatalogExtension.scala @@ -7,7 +7,7 @@ import io.circe.syntax._ trait CatalogExtension[T] { def getExtensionFields(catalog: StacCatalog): ExtensionResult[T] - def extend(catalog: StacCatalog, extensionFields: T): StacCatalog + def addExtensionFields(catalog: StacCatalog, extensionFields: T): StacCatalog } object CatalogExtension { @@ -19,7 +19,7 @@ object CatalogExtension { def getExtensionFields(catalog: StacCatalog): ExtensionResult[T] = decoder.decodeAccumulating(catalog.extensionFields.asJson.hcursor) - def extend(catalog: StacCatalog, extensionFields: T): StacCatalog = + def addExtensionFields(catalog: StacCatalog, extensionFields: T): StacCatalog = catalog.copy(extensionFields = catalog.extensionFields.deepMerge(objectEncoder.encodeObject(extensionFields))) } } diff --git a/modules/core/src/main/scala/com/azavea/stac4s/extensions/CollectionExtension.scala b/modules/core/src/main/scala/com/azavea/stac4s/extensions/CollectionExtension.scala index 23c69d05..3e050d71 100644 --- a/modules/core/src/main/scala/com/azavea/stac4s/extensions/CollectionExtension.scala +++ b/modules/core/src/main/scala/com/azavea/stac4s/extensions/CollectionExtension.scala @@ -8,7 +8,7 @@ import io.circe.syntax._ trait CollectionExtension[T] { def getExtensionFields(collection: StacCollection): ExtensionResult[T] - def extend(collection: StacCollection, extensionFields: T): StacCollection + def addExtensionFields(collection: StacCollection, extensionFields: T): StacCollection } object CollectionExtension { @@ -20,7 +20,7 @@ object CollectionExtension { def getExtensionFields(collection: StacCollection): ExtensionResult[T] = decoder.decodeAccumulating(collection.extensionFields.asJson.hcursor) - def extend(collection: StacCollection, extensionFields: T): StacCollection = + def addExtensionFields(collection: StacCollection, extensionFields: T): StacCollection = collection.copy(extensionFields = collection.extensionFields.deepMerge(objectEncoder.encodeObject(extensionFields))) } } diff --git a/modules/core/src/main/scala/com/azavea/stac4s/extensions/ItemAssetExtension.scala b/modules/core/src/main/scala/com/azavea/stac4s/extensions/ItemAssetExtension.scala index b994f08c..3b910f39 100644 --- a/modules/core/src/main/scala/com/azavea/stac4s/extensions/ItemAssetExtension.scala +++ b/modules/core/src/main/scala/com/azavea/stac4s/extensions/ItemAssetExtension.scala @@ -7,7 +7,7 @@ import io.circe.syntax._ trait ItemAssetExtension[T] { def getExtensionFields(asset: StacItemAsset): ExtensionResult[T] - def extend(asset: StacItemAsset, extensionFields: T): StacItemAsset + def addExtensionFields(asset: StacItemAsset, extensionFields: T): StacItemAsset } object ItemAssetExtension { @@ -19,7 +19,7 @@ object ItemAssetExtension { def getExtensionFields(asset: StacItemAsset): ExtensionResult[T] = decoder.decodeAccumulating(asset.extensionFields.asJson.hcursor) - def extend(asset: StacItemAsset, extensionFields: T): StacItemAsset = + def addExtensionFields(asset: StacItemAsset, extensionFields: T): StacItemAsset = asset.copy(extensionFields = asset.extensionFields.deepMerge(objectEncoder.encodeObject(extensionFields))) } } diff --git a/modules/core/src/main/scala/com/azavea/stac4s/extensions/ItemCollectionExtension.scala b/modules/core/src/main/scala/com/azavea/stac4s/extensions/ItemCollectionExtension.scala index bfa32390..12ee2cca 100644 --- a/modules/core/src/main/scala/com/azavea/stac4s/extensions/ItemCollectionExtension.scala +++ b/modules/core/src/main/scala/com/azavea/stac4s/extensions/ItemCollectionExtension.scala @@ -7,7 +7,7 @@ import io.circe.syntax._ trait ItemCollectionExtension[T] { def getExtensionFields(itemCollection: ItemCollection): ExtensionResult[T] - def extend(itemCollection: ItemCollection, properties: T): ItemCollection + def addExtensionFields(itemCollection: ItemCollection, properties: T): ItemCollection } object ItemExtensionCollection { @@ -21,7 +21,7 @@ object ItemExtensionCollection { itemCollection.extensionFields.asJson.hcursor ) - def extend(itemCollection: ItemCollection, extensionProperties: T): ItemCollection = + def addExtensionFields(itemCollection: ItemCollection, extensionProperties: T): ItemCollection = itemCollection.copy(extensionFields = itemCollection.extensionFields.deepMerge(objectEncoder.encodeObject(extensionProperties)) ) diff --git a/modules/core/src/main/scala/com/azavea/stac4s/extensions/ItemExtension.scala b/modules/core/src/main/scala/com/azavea/stac4s/extensions/ItemExtension.scala index e58ec5a7..968ec3d2 100644 --- a/modules/core/src/main/scala/com/azavea/stac4s/extensions/ItemExtension.scala +++ b/modules/core/src/main/scala/com/azavea/stac4s/extensions/ItemExtension.scala @@ -8,7 +8,7 @@ import io.circe.syntax._ // typeclass trait for anything that is an extension of item properties trait ItemExtension[T] { def getExtensionFields(item: StacItem): ExtensionResult[T] - def extend(item: StacItem, properties: T): StacItem + def addExtensionFields(item: StacItem, properties: T): StacItem } object ItemExtension { @@ -24,7 +24,7 @@ object ItemExtension { item.properties.asJson.hcursor ) - def extend(item: StacItem, extensionProperties: T) = + def addExtensionFields(item: StacItem, extensionProperties: T) = item.copy(properties = item.properties.deepMerge(objectEncoder.encodeObject(extensionProperties))) } } diff --git a/modules/core/src/main/scala/com/azavea/stac4s/extensions/LinkExtension.scala b/modules/core/src/main/scala/com/azavea/stac4s/extensions/LinkExtension.scala index 16aabe9a..98156332 100644 --- a/modules/core/src/main/scala/com/azavea/stac4s/extensions/LinkExtension.scala +++ b/modules/core/src/main/scala/com/azavea/stac4s/extensions/LinkExtension.scala @@ -8,7 +8,7 @@ import io.circe.syntax._ trait LinkExtension[T] { def getExtensionFields(link: StacLink): ExtensionResult[T] - def extend(link: StacLink, extensionFields: T): StacLink + def addExtensionFields(link: StacLink, extensionFields: T): StacLink } object LinkExtension { @@ -20,7 +20,7 @@ object LinkExtension { def getExtensionFields(link: StacLink): ExtensionResult[T] = decoder.decodeAccumulating(link.extensionFields.asJson.hcursor) - def extend(link: StacLink, extensionFields: T): StacLink = + def addExtensionFields(link: StacLink, extensionFields: T): StacLink = link.copy(extensionFields = link.extensionFields.deepMerge(objectEncoder.encodeObject(extensionFields)) ) From 6dcbdc173a33f8b2784f75e51b23922273521dff Mon Sep 17 00:00:00 2001 From: James Santucci Date: Tue, 19 May 2020 08:56:05 -0500 Subject: [PATCH 22/26] fix and test layer item extension decoder --- .../stac4s/extensions/layer/LayerItemExtension.scala | 8 ++------ .../test/scala/com/azavea/stac4s/CatalogLayerSpec.scala | 4 ++++ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/core/src/main/scala/com/azavea/stac4s/extensions/layer/LayerItemExtension.scala b/modules/core/src/main/scala/com/azavea/stac4s/extensions/layer/LayerItemExtension.scala index 7f387c42..53b3891c 100644 --- a/modules/core/src/main/scala/com/azavea/stac4s/extensions/layer/LayerItemExtension.scala +++ b/modules/core/src/main/scala/com/azavea/stac4s/extensions/layer/LayerItemExtension.scala @@ -17,12 +17,8 @@ object LayerItemExtension { implicit val encLayerProperties: Encoder.AsObject[LayerItemExtension] = Encoder.AsObject.instance[LayerItemExtension] { o => Map("layer:ids" -> o.ids.asJson).asJsonObject } - implicit val decLayerProperties: Decoder[LayerItemExtension] = Decoder[Map[String, NonEmptyList[NonEmptyString]]] emap { - _.get("layer:ids") match { - case Some(l) => Right(LayerItemExtension(l)) - case _ => Left("Could not decode LayerProperties.") - } - } + implicit val decLayerProperties: Decoder[LayerItemExtension] = + Decoder.forProduct1("layer:ids")(LayerItemExtension.apply) implicit val itemExtension: ItemExtension[LayerItemExtension] = ItemExtension.instance } diff --git a/modules/core/src/test/scala/com/azavea/stac4s/CatalogLayerSpec.scala b/modules/core/src/test/scala/com/azavea/stac4s/CatalogLayerSpec.scala index fb1413e5..61799ac7 100644 --- a/modules/core/src/test/scala/com/azavea/stac4s/CatalogLayerSpec.scala +++ b/modules/core/src/test/scala/com/azavea/stac4s/CatalogLayerSpec.scala @@ -1,5 +1,6 @@ package com.azavea.stac4s +import com.azavea.stac4s.extensions.ItemExtension import com.azavea.stac4s.extensions.layer._ import cats.data.NonEmptyList import cats.implicits._ @@ -304,6 +305,9 @@ class CatalogLayerSpec extends AnyFunSpec with Matchers { collection = collection.id.some ) + ItemExtension[LayerItemExtension].getExtensionFields(item) shouldBe LayerItemExtension( + NonEmptyList.fromListUnsafe(List("layer-us", "layer-ca") map { NonEmptyString.unsafeFrom }) + ).valid item.asJson.deepDropNullValues shouldBe getJson( "/catalogs/landsat-stac-layers/landsat-8-l1/2014-153/LC81530252014153LGN00.json" ) From 853d9b66bb51df9b34c33ce046a178bb2f808f72 Mon Sep 17 00:00:00 2001 From: James Santucci Date: Tue, 19 May 2020 09:11:14 -0500 Subject: [PATCH 23/26] test that item ser-de works with extensions --- .../src/test/scala/com/azavea/stac4s/Generators.scala | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/modules/core/src/test/scala/com/azavea/stac4s/Generators.scala b/modules/core/src/test/scala/com/azavea/stac4s/Generators.scala index f2cf4b2f..af2479f6 100644 --- a/modules/core/src/test/scala/com/azavea/stac4s/Generators.scala +++ b/modules/core/src/test/scala/com/azavea/stac4s/Generators.scala @@ -49,6 +49,12 @@ object Generators { .map(_.asJsonObject) ) + private def itemExtensionFieldsGen: Gen[JsonObject] = Gen.oneOf( + Gen.const(().asJsonObject), + labelExtensionPropertiesGen map { _.asJsonObject }, + layerPropertiesGen map { _.asJsonObject } + ) + private def linkExtensionFields: Gen[JsonObject] = Gen.oneOf( Gen.const(().asJsonObject), Gen @@ -209,7 +215,7 @@ object Generators { Gen.nonEmptyListOf(stacLinkGen), Gen.nonEmptyMap((nonEmptyStringGen, cogAssetGen).tupled), Gen.option(nonEmptyStringGen), - Gen.const(JsonObject.fromMap(Map.empty)) + itemExtensionFieldsGen ).mapN(StacItem.apply) private def stacCatalogGen: Gen[StacCatalog] = From ade5ae98d12c40786730d9bdc26337dfe794badc Mon Sep 17 00:00:00 2001 From: James Santucci Date: Tue, 19 May 2020 09:54:41 -0500 Subject: [PATCH 24/26] derive case class fields with shapeless --- .../com/azavea/stac4s/ItemCollection.scala | 12 +++++------- .../scala/com/azavea/stac4s/StacCatalog.scala | 13 +++++-------- .../com/azavea/stac4s/StacCollection.scala | 19 +++++-------------- .../com/azavea/stac4s/StacItemAsset.scala | 12 +++++------- .../scala/com/azavea/stac4s/StacLink.scala | 6 +++++- .../scala/com/azavea/stac4s/package.scala | 7 +++++++ 6 files changed, 32 insertions(+), 37 deletions(-) diff --git a/modules/core/src/main/scala/com/azavea/stac4s/ItemCollection.scala b/modules/core/src/main/scala/com/azavea/stac4s/ItemCollection.scala index 4d4732ce..470ab32c 100644 --- a/modules/core/src/main/scala/com/azavea/stac4s/ItemCollection.scala +++ b/modules/core/src/main/scala/com/azavea/stac4s/ItemCollection.scala @@ -5,6 +5,8 @@ import cats.implicits._ import io.circe._ import io.circe.refined._ import io.circe.syntax._ +import shapeless.LabelledGeneric +import shapeless.ops.record.Keys final case class ItemCollection( _type: String = "FeatureCollection", @@ -17,13 +19,9 @@ final case class ItemCollection( object ItemCollection { - val itemCollectionFields = Set( - "type", - "stac_version", - "stac_extensions", - "features", - "links" - ) + private val generic = LabelledGeneric[ItemCollection] + private val keys = Keys[generic.Repr].apply + val itemCollectionFields = keys.toList.flatMap(field => substituteFieldName(field.name)).toSet implicit val eqItemCollection: Eq[ItemCollection] = Eq.fromUniversalEquals diff --git a/modules/core/src/main/scala/com/azavea/stac4s/StacCatalog.scala b/modules/core/src/main/scala/com/azavea/stac4s/StacCatalog.scala index e77681b1..d12a6af3 100644 --- a/modules/core/src/main/scala/com/azavea/stac4s/StacCatalog.scala +++ b/modules/core/src/main/scala/com/azavea/stac4s/StacCatalog.scala @@ -4,6 +4,8 @@ import cats.Eq import cats.implicits._ import io.circe._ import io.circe.syntax._ +import shapeless.LabelledGeneric +import shapeless.ops.record.Keys final case class StacCatalog( stacVersion: String, @@ -17,14 +19,9 @@ final case class StacCatalog( object StacCatalog { - val catalogFields = Set( - "stac_version", - "stac_extensions", - "id", - "title", - "description", - "links" - ) + private val generic = LabelledGeneric[StacCatalog] + private val keys = Keys[generic.Repr].apply + val catalogFields = keys.toList.flatMap(field => substituteFieldName(field.name)).toSet implicit val eqStacCatalog: Eq[StacCatalog] = Eq.fromUniversalEquals diff --git a/modules/core/src/main/scala/com/azavea/stac4s/StacCollection.scala b/modules/core/src/main/scala/com/azavea/stac4s/StacCollection.scala index 7373a996..de676075 100644 --- a/modules/core/src/main/scala/com/azavea/stac4s/StacCollection.scala +++ b/modules/core/src/main/scala/com/azavea/stac4s/StacCollection.scala @@ -5,6 +5,8 @@ import cats.implicits._ import geotrellis.vector.{io => _} import io.circe._ import io.circe.syntax._ +import shapeless.LabelledGeneric +import shapeless.ops.record.Keys final case class StacCollection( stacVersion: String, @@ -24,20 +26,9 @@ final case class StacCollection( object StacCollection { - val collectionFields = Set( - "stac_version", - "stac_extensions", - "id", - "title", - "description", - "keywords", - "license", - "providers", - "extent", - "summaries", - "properties", - "links" - ) + private val generic = LabelledGeneric[StacCollection] + private val keys = Keys[generic.Repr].apply + val collectionFields = keys.toList.flatMap(field => substituteFieldName(field.name)).toSet implicit val eqStacCollection: Eq[StacCollection] = Eq.fromUniversalEquals diff --git a/modules/core/src/main/scala/com/azavea/stac4s/StacItemAsset.scala b/modules/core/src/main/scala/com/azavea/stac4s/StacItemAsset.scala index 0650cdd2..4aeaf44e 100644 --- a/modules/core/src/main/scala/com/azavea/stac4s/StacItemAsset.scala +++ b/modules/core/src/main/scala/com/azavea/stac4s/StacItemAsset.scala @@ -4,6 +4,8 @@ import cats.Eq import cats.implicits._ import io.circe._ import io.circe.syntax._ +import shapeless.LabelledGeneric +import shapeless.ops.record.Keys final case class StacItemAsset( href: String, @@ -16,13 +18,9 @@ final case class StacItemAsset( object StacItemAsset { - val assetFields = Set( - "href", - "title", - "description", - "roles", - "type" - ) + private val generic = LabelledGeneric[StacItemAsset] + private val keys = Keys[generic.Repr].apply + val assetFields = keys.toList.flatMap(field => substituteFieldName(field.name)).toSet implicit val eqStacItemAsset: Eq[StacItemAsset] = Eq.fromUniversalEquals diff --git a/modules/core/src/main/scala/com/azavea/stac4s/StacLink.scala b/modules/core/src/main/scala/com/azavea/stac4s/StacLink.scala index 55a22d59..8b41f78f 100644 --- a/modules/core/src/main/scala/com/azavea/stac4s/StacLink.scala +++ b/modules/core/src/main/scala/com/azavea/stac4s/StacLink.scala @@ -3,6 +3,8 @@ package com.azavea.stac4s import cats.implicits._ import io.circe._ import io.circe.syntax._ +import shapeless.LabelledGeneric +import shapeless.ops.record.Keys final case class StacLink( href: String, @@ -14,7 +16,9 @@ final case class StacLink( object StacLink { - val linkFields = Set("href", "rel", "type", "title") + private val generic = LabelledGeneric[StacLink] + private val keys = Keys[generic.Repr].apply + val linkFields = keys.toList.flatMap(field => substituteFieldName(field.name)).toSet implicit val encStacLink: Encoder[StacLink] = new Encoder[StacLink] { diff --git a/modules/core/src/main/scala/com/azavea/stac4s/package.scala b/modules/core/src/main/scala/com/azavea/stac4s/package.scala index 71026161..d5548a5b 100644 --- a/modules/core/src/main/scala/com/azavea/stac4s/package.scala +++ b/modules/core/src/main/scala/com/azavea/stac4s/package.scala @@ -39,4 +39,11 @@ package object stac4s { implicit val eqTemporalExtent: Eq[TemporalExtent] = Eq.fromUniversalEquals + def substituteFieldName(fieldName: String): Option[String] = fieldName match { + case "_type" => Some("type") + case "stacVersion" => Some("stac_version") + case "stacExtensions" => Some("stac_extensions") + case "extensionFields" => None + case s => Some(s) + } } From 36721b3fa732217859c5eb04fa3c7ed96f5c5db9 Mon Sep 17 00:00:00 2001 From: James Santucci Date: Tue, 19 May 2020 10:48:07 -0500 Subject: [PATCH 25/26] add syntax package and test where possible --- .../com/azavea/stac4s/syntax/package.scala | 47 +++++++++++++++ .../scala/com/azavea/stac4s/Generators.scala | 28 +++++++-- .../scala/com/azavea/stac4s/SyntaxSpec.scala | 60 +++++++++++++++++++ 3 files changed, 129 insertions(+), 6 deletions(-) create mode 100644 modules/core/src/main/scala/com/azavea/stac4s/syntax/package.scala create mode 100644 modules/core/src/test/scala/com/azavea/stac4s/SyntaxSpec.scala diff --git a/modules/core/src/main/scala/com/azavea/stac4s/syntax/package.scala b/modules/core/src/main/scala/com/azavea/stac4s/syntax/package.scala new file mode 100644 index 00000000..6f8993fd --- /dev/null +++ b/modules/core/src/main/scala/com/azavea/stac4s/syntax/package.scala @@ -0,0 +1,47 @@ +package com.azavea.stac4s + +import com.azavea.stac4s.extensions._ + +package object syntax { + implicit class stacItemExtensions(item: StacItem) { + def getExtensionFields[T](implicit ev: ItemExtension[T]): ExtensionResult[T] = + ev.getExtensionFields(item) + def addExtensionFields[T](properties: T)(implicit ev: ItemExtension[T]): StacItem = + ev.addExtensionFields(item, properties) + } + + implicit class stacCollectionExtensions(collection: StacCollection) { + def getExtensionFields[T](implicit ev: CollectionExtension[T]): ExtensionResult[T] = + ev.getExtensionFields(collection) + def addExtensionFields[T](properties: T)(implicit ev: CollectionExtension[T]): StacCollection = + ev.addExtensionFields(collection, properties) + } + + implicit class stacCatalogExtensions(catalog: StacCatalog) { + def getExtensionFields[T](implicit ev: CatalogExtension[T]): ExtensionResult[T] = + ev.getExtensionFields(catalog) + def addExtensionFields[T](properties: T)(implicit ev: CatalogExtension[T]): StacCatalog = + ev.addExtensionFields(catalog, properties) + } + + implicit class stacItemAssetExtensions(itemAsset: StacItemAsset) { + def getExtensionFields[T](implicit ev: ItemAssetExtension[T]): ExtensionResult[T] = + ev.getExtensionFields(itemAsset) + def addExtensionFields[T](properties: T)(implicit ev: ItemAssetExtension[T]): StacItemAsset = + ev.addExtensionFields(itemAsset, properties) + } + + implicit class stacLinkExtensions(link: StacLink) { + def getExtensionFields[T](implicit ev: LinkExtension[T]): ExtensionResult[T] = + ev.getExtensionFields(link) + def addExtensionFields[T](properties: T)(implicit ev: LinkExtension[T]): StacLink = + ev.addExtensionFields(link, properties) + } + + implicit class stacItemCollectionExtensions(itemCollection: ItemCollection) { + def getExtensionFields[T](implicit ev: ItemCollectionExtension[T]): ExtensionResult[T] = + ev.getExtensionFields(itemCollection) + def addExtensionFields[T](properties: T)(implicit ev: ItemCollectionExtension[T]): ItemCollection = + ev.addExtensionFields(itemCollection, properties) + } +} diff --git a/modules/core/src/test/scala/com/azavea/stac4s/Generators.scala b/modules/core/src/test/scala/com/azavea/stac4s/Generators.scala index af2479f6..e29fa7a8 100644 --- a/modules/core/src/test/scala/com/azavea/stac4s/Generators.scala +++ b/modules/core/src/test/scala/com/azavea/stac4s/Generators.scala @@ -39,14 +39,16 @@ object Generators { private def instantGen: Gen[Instant] = arbitrary[Int] map { x => Instant.now.plusMillis(x.toLong) } - private def collectionExtensionFieldsGen: Gen[JsonObject] = Gen.oneOf( - Gen.const(().asJsonObject), + private def assetCollectionExtensionGen: Gen[AssetCollectionExtension] = Gen .mapOf( (nonEmptyStringGen, stacCollectionAssetGen).tupled ) .map(AssetCollectionExtension.apply) - .map(_.asJsonObject) + + private def collectionExtensionFieldsGen: Gen[JsonObject] = Gen.oneOf( + Gen.const(().asJsonObject), + assetCollectionExtensionGen.map(_.asJsonObject) ) private def itemExtensionFieldsGen: Gen[JsonObject] = Gen.oneOf( @@ -55,13 +57,15 @@ object Generators { layerPropertiesGen map { _.asJsonObject } ) - private def linkExtensionFields: Gen[JsonObject] = Gen.oneOf( - Gen.const(().asJsonObject), + private def labelLinkExtensionGen: Gen[LabelLinkExtension] = Gen .nonEmptyListOf(nonEmptyStringGen) .map(NonEmptyList.fromListUnsafe) .map(LabelLinkExtension.apply) - .map(_.asJsonObject) + + private def linkExtensionFields: Gen[JsonObject] = Gen.oneOf( + Gen.const(().asJsonObject), + labelLinkExtensionGen.map(_.asJsonObject) ) private def mediaTypeGen: Gen[StacMediaType] = Gen.oneOf( @@ -409,6 +413,10 @@ object Generators { assetRoleGen } + implicit val arbStacLink: Arbitrary[StacLink] = Arbitrary { + stacLinkGen + } + implicit val arbLabelClassName: Arbitrary[LabelClassName] = Arbitrary { labelClassNameGen } implicit val arbLabelClassClasses: Arbitrary[LabelClassClasses] = Arbitrary { labelClassClassesGen } @@ -433,5 +441,13 @@ object Generators { labelExtensionPropertiesGen } + implicit val arbLabelLinkExtension: Arbitrary[LabelLinkExtension] = Arbitrary { + labelLinkExtensionGen + } + implicit val arbLayerProperties: Arbitrary[LayerItemExtension] = Arbitrary { layerPropertiesGen } + + implicit val arbAssetExtensionProperties: Arbitrary[AssetCollectionExtension] = Arbitrary { + assetCollectionExtensionGen + } } diff --git a/modules/core/src/test/scala/com/azavea/stac4s/SyntaxSpec.scala b/modules/core/src/test/scala/com/azavea/stac4s/SyntaxSpec.scala new file mode 100644 index 00000000..7e5368e5 --- /dev/null +++ b/modules/core/src/test/scala/com/azavea/stac4s/SyntaxSpec.scala @@ -0,0 +1,60 @@ +package com.azavea.stac4s + +import com.azavea.stac4s.syntax._ +import com.azavea.stac4s.extensions._ +import com.azavea.stac4s.extensions.asset._ +import com.azavea.stac4s.extensions.label._ +import Generators._ +import cats.implicits._ +import io.circe.syntax._ +import org.scalatest.funsuite.AnyFunSuite +import org.scalatest.matchers.should.Matchers +import org.scalatestplus.scalacheck.Checkers + +class SyntaxSpec extends AnyFunSuite with Checkers with Matchers { + test("item syntax results in the same values as typeclass summoner to extend") { + check { (item: StacItem, labelExtension: LabelItemExtension) => + item.addExtensionFields(labelExtension) == ItemExtension[LabelItemExtension] + .addExtensionFields(item, labelExtension) + } + } + + test("item syntax results in the same values as typeclass summoner to parse") { + check { (item: StacItem, labelExtension: LabelItemExtension) => + item.addExtensionFields(labelExtension).getExtensionFields[LabelItemExtension] == + labelExtension.valid + } + } + + test("collection syntax results in the same values as typeclass summoner to extend") { + check { (collection: StacCollection, assetExtension: AssetCollectionExtension) => + collection.addExtensionFields(assetExtension) == CollectionExtension[AssetCollectionExtension] + .addExtensionFields(collection, assetExtension) + } + } + + test("collection syntax results in the same values as typeclass summoner to parse") { + check { (collection: StacCollection, assetExtension: AssetCollectionExtension) => + // We have to nuke existing extensions, because otherwise the collection can start with + // some assets in its asset extension, in which case the decoded result will have extra + // data + val withoutExtensions = collection.copy(extensionFields = ().asJsonObject) + withoutExtensions + .addExtensionFields(assetExtension) + .getExtensionFields[AssetCollectionExtension] == assetExtension.valid + } + } + + test("link syntax results in the same values as typeclass summoner to extend") { + check { (stacLink: StacLink, labelLinkExtension: LabelLinkExtension) => + stacLink.addExtensionFields(labelLinkExtension) == LinkExtension[LabelLinkExtension] + .addExtensionFields(stacLink, labelLinkExtension) + } + } + + test("link syntax results in the same values as typeclass summoner to parse") { + check { (stacLink: StacLink, labelLinkExtension: LabelLinkExtension) => + stacLink.addExtensionFields(labelLinkExtension).getExtensionFields[LabelLinkExtension] == labelLinkExtension.valid + } + } +} From 8f0ad3e809de1943056766479d5044c32ac324bd Mon Sep 17 00:00:00 2001 From: James Santucci Date: Tue, 19 May 2020 10:48:32 -0500 Subject: [PATCH 26/26] lint --- .../scala/com/azavea/stac4s/ItemCollection.scala | 4 ++-- .../scala/com/azavea/stac4s/StacCatalog.scala | 6 +++--- .../scala/com/azavea/stac4s/StacCollection.scala | 4 ++-- .../scala/com/azavea/stac4s/StacItemAsset.scala | 6 +++--- .../main/scala/com/azavea/stac4s/StacLink.scala | 6 +++--- .../stac4s/extensions/CollectionExtension.scala | 4 +++- .../azavea/stac4s/extensions/LinkExtension.scala | 4 +--- .../scala/com/azavea/stac4s/syntax/package.scala | 15 ++++++++++++++- 8 files changed, 31 insertions(+), 18 deletions(-) diff --git a/modules/core/src/main/scala/com/azavea/stac4s/ItemCollection.scala b/modules/core/src/main/scala/com/azavea/stac4s/ItemCollection.scala index 470ab32c..4725b365 100644 --- a/modules/core/src/main/scala/com/azavea/stac4s/ItemCollection.scala +++ b/modules/core/src/main/scala/com/azavea/stac4s/ItemCollection.scala @@ -19,8 +19,8 @@ final case class ItemCollection( object ItemCollection { - private val generic = LabelledGeneric[ItemCollection] - private val keys = Keys[generic.Repr].apply + private val generic = LabelledGeneric[ItemCollection] + private val keys = Keys[generic.Repr].apply val itemCollectionFields = keys.toList.flatMap(field => substituteFieldName(field.name)).toSet implicit val eqItemCollection: Eq[ItemCollection] = Eq.fromUniversalEquals diff --git a/modules/core/src/main/scala/com/azavea/stac4s/StacCatalog.scala b/modules/core/src/main/scala/com/azavea/stac4s/StacCatalog.scala index d12a6af3..6ef62609 100644 --- a/modules/core/src/main/scala/com/azavea/stac4s/StacCatalog.scala +++ b/modules/core/src/main/scala/com/azavea/stac4s/StacCatalog.scala @@ -19,9 +19,9 @@ final case class StacCatalog( object StacCatalog { - private val generic = LabelledGeneric[StacCatalog] - private val keys = Keys[generic.Repr].apply - val catalogFields = keys.toList.flatMap(field => substituteFieldName(field.name)).toSet + private val generic = LabelledGeneric[StacCatalog] + private val keys = Keys[generic.Repr].apply + val catalogFields = keys.toList.flatMap(field => substituteFieldName(field.name)).toSet implicit val eqStacCatalog: Eq[StacCatalog] = Eq.fromUniversalEquals diff --git a/modules/core/src/main/scala/com/azavea/stac4s/StacCollection.scala b/modules/core/src/main/scala/com/azavea/stac4s/StacCollection.scala index de676075..1461dd34 100644 --- a/modules/core/src/main/scala/com/azavea/stac4s/StacCollection.scala +++ b/modules/core/src/main/scala/com/azavea/stac4s/StacCollection.scala @@ -26,8 +26,8 @@ final case class StacCollection( object StacCollection { - private val generic = LabelledGeneric[StacCollection] - private val keys = Keys[generic.Repr].apply + private val generic = LabelledGeneric[StacCollection] + private val keys = Keys[generic.Repr].apply val collectionFields = keys.toList.flatMap(field => substituteFieldName(field.name)).toSet implicit val eqStacCollection: Eq[StacCollection] = Eq.fromUniversalEquals diff --git a/modules/core/src/main/scala/com/azavea/stac4s/StacItemAsset.scala b/modules/core/src/main/scala/com/azavea/stac4s/StacItemAsset.scala index 4aeaf44e..400322be 100644 --- a/modules/core/src/main/scala/com/azavea/stac4s/StacItemAsset.scala +++ b/modules/core/src/main/scala/com/azavea/stac4s/StacItemAsset.scala @@ -18,9 +18,9 @@ final case class StacItemAsset( object StacItemAsset { - private val generic = LabelledGeneric[StacItemAsset] - private val keys = Keys[generic.Repr].apply - val assetFields = keys.toList.flatMap(field => substituteFieldName(field.name)).toSet + private val generic = LabelledGeneric[StacItemAsset] + private val keys = Keys[generic.Repr].apply + val assetFields = keys.toList.flatMap(field => substituteFieldName(field.name)).toSet implicit val eqStacItemAsset: Eq[StacItemAsset] = Eq.fromUniversalEquals diff --git a/modules/core/src/main/scala/com/azavea/stac4s/StacLink.scala b/modules/core/src/main/scala/com/azavea/stac4s/StacLink.scala index 8b41f78f..81bdee97 100644 --- a/modules/core/src/main/scala/com/azavea/stac4s/StacLink.scala +++ b/modules/core/src/main/scala/com/azavea/stac4s/StacLink.scala @@ -16,9 +16,9 @@ final case class StacLink( object StacLink { - private val generic = LabelledGeneric[StacLink] - private val keys = Keys[generic.Repr].apply - val linkFields = keys.toList.flatMap(field => substituteFieldName(field.name)).toSet + private val generic = LabelledGeneric[StacLink] + private val keys = Keys[generic.Repr].apply + val linkFields = keys.toList.flatMap(field => substituteFieldName(field.name)).toSet implicit val encStacLink: Encoder[StacLink] = new Encoder[StacLink] { diff --git a/modules/core/src/main/scala/com/azavea/stac4s/extensions/CollectionExtension.scala b/modules/core/src/main/scala/com/azavea/stac4s/extensions/CollectionExtension.scala index 3e050d71..4306b832 100644 --- a/modules/core/src/main/scala/com/azavea/stac4s/extensions/CollectionExtension.scala +++ b/modules/core/src/main/scala/com/azavea/stac4s/extensions/CollectionExtension.scala @@ -21,6 +21,8 @@ object CollectionExtension { decoder.decodeAccumulating(collection.extensionFields.asJson.hcursor) def addExtensionFields(collection: StacCollection, extensionFields: T): StacCollection = - collection.copy(extensionFields = collection.extensionFields.deepMerge(objectEncoder.encodeObject(extensionFields))) + collection.copy(extensionFields = + collection.extensionFields.deepMerge(objectEncoder.encodeObject(extensionFields)) + ) } } diff --git a/modules/core/src/main/scala/com/azavea/stac4s/extensions/LinkExtension.scala b/modules/core/src/main/scala/com/azavea/stac4s/extensions/LinkExtension.scala index 98156332..fdfc9e59 100644 --- a/modules/core/src/main/scala/com/azavea/stac4s/extensions/LinkExtension.scala +++ b/modules/core/src/main/scala/com/azavea/stac4s/extensions/LinkExtension.scala @@ -21,8 +21,6 @@ object LinkExtension { decoder.decodeAccumulating(link.extensionFields.asJson.hcursor) def addExtensionFields(link: StacLink, extensionFields: T): StacLink = - link.copy(extensionFields = - link.extensionFields.deepMerge(objectEncoder.encodeObject(extensionFields)) - ) + link.copy(extensionFields = link.extensionFields.deepMerge(objectEncoder.encodeObject(extensionFields))) } } diff --git a/modules/core/src/main/scala/com/azavea/stac4s/syntax/package.scala b/modules/core/src/main/scala/com/azavea/stac4s/syntax/package.scala index 6f8993fd..f2b093b4 100644 --- a/modules/core/src/main/scala/com/azavea/stac4s/syntax/package.scala +++ b/modules/core/src/main/scala/com/azavea/stac4s/syntax/package.scala @@ -3,44 +3,57 @@ package com.azavea.stac4s import com.azavea.stac4s.extensions._ package object syntax { + implicit class stacItemExtensions(item: StacItem) { + def getExtensionFields[T](implicit ev: ItemExtension[T]): ExtensionResult[T] = ev.getExtensionFields(item) + def addExtensionFields[T](properties: T)(implicit ev: ItemExtension[T]): StacItem = ev.addExtensionFields(item, properties) } implicit class stacCollectionExtensions(collection: StacCollection) { + def getExtensionFields[T](implicit ev: CollectionExtension[T]): ExtensionResult[T] = ev.getExtensionFields(collection) + def addExtensionFields[T](properties: T)(implicit ev: CollectionExtension[T]): StacCollection = ev.addExtensionFields(collection, properties) } implicit class stacCatalogExtensions(catalog: StacCatalog) { + def getExtensionFields[T](implicit ev: CatalogExtension[T]): ExtensionResult[T] = ev.getExtensionFields(catalog) + def addExtensionFields[T](properties: T)(implicit ev: CatalogExtension[T]): StacCatalog = ev.addExtensionFields(catalog, properties) } implicit class stacItemAssetExtensions(itemAsset: StacItemAsset) { + def getExtensionFields[T](implicit ev: ItemAssetExtension[T]): ExtensionResult[T] = ev.getExtensionFields(itemAsset) + def addExtensionFields[T](properties: T)(implicit ev: ItemAssetExtension[T]): StacItemAsset = ev.addExtensionFields(itemAsset, properties) } implicit class stacLinkExtensions(link: StacLink) { + def getExtensionFields[T](implicit ev: LinkExtension[T]): ExtensionResult[T] = ev.getExtensionFields(link) + def addExtensionFields[T](properties: T)(implicit ev: LinkExtension[T]): StacLink = ev.addExtensionFields(link, properties) } - + implicit class stacItemCollectionExtensions(itemCollection: ItemCollection) { + def getExtensionFields[T](implicit ev: ItemCollectionExtension[T]): ExtensionResult[T] = ev.getExtensionFields(itemCollection) + def addExtensionFields[T](properties: T)(implicit ev: ItemCollectionExtension[T]): ItemCollection = ev.addExtensionFields(itemCollection, properties) }