Skip to content

Commit

Permalink
Merge pull request #153 from planetlabs/extensions
Browse files Browse the repository at this point in the history
Reworked extension handling
  • Loading branch information
tschaub authored Nov 28, 2023
2 parents fc74da2 + 32fa30c commit c65a270
Show file tree
Hide file tree
Showing 18 changed files with 1,339 additions and 163 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/[email protected]
with:
go-version: '1.20'
go-version: '1.21'
- uses: golangci/golangci-lint-action@v2
with:
version: v1.51.2
version: v1.54.1
args: "--out-${NO_FUTURE}format colored-line-number"

test:
Expand All @@ -27,5 +27,5 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/[email protected]
with:
go-version: '1.20'
go-version: '1.21'
- run: go test -v ./...
69 changes: 36 additions & 33 deletions asset.go
Original file line number Diff line number Diff line change
@@ -1,50 +1,53 @@
package stac

import (
"encoding/json"
"fmt"
"regexp"

"github.com/mitchellh/mapstructure"
)

type Asset struct {
Type string `json:"type,omitempty"`
Href string `json:"href"`
Title string `json:"title,omitempty"`
Description string `json:"description,omitempty"`
Created string `json:"created,omitempty"`
Roles []string `json:"roles,omitempty"`
Extensions []AssetExtension `json:"-"`
Type string `json:"type,omitempty"`
Href string `json:"href,omitempty"`
Title string `json:"title,omitempty"`
Description string `json:"description,omitempty"`
Created string `json:"created,omitempty"`
Roles []string `json:"roles,omitempty"`
Extensions []Extension `json:"-"`
}

var _ json.Marshaler = (*Asset)(nil)
var assetExtensions = newExtensionRegistry()

type AssetExtension interface {
Apply(*Asset)
URI() string
func RegisterAssetExtension(pattern *regexp.Regexp, provider ExtensionProvider) {
assetExtensions.register(pattern, provider)
}

func (asset Asset) MarshalJSON() ([]byte, error) {
assetMap := map[string]any{}
decoder, decoderErr := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
TagName: "json",
Result: &assetMap,
})
if decoderErr != nil {
return nil, decoderErr
}

decodeErr := decoder.Decode(asset)
if decodeErr != nil {
return nil, decodeErr
}
func GetAssetExtension(uri string) Extension {
return assetExtensions.get(uri)
}

for _, extension := range asset.Extensions {
extension.Apply(&asset)
if decodeErr := decoder.Decode(extension); decodeErr != nil {
return nil, fmt.Errorf("trouble encoding JSON for %s asset: %w", extension.URI(), decodeErr)
func EncodeAssets(assets map[string]*Asset) (map[string]any, []string, error) {
assetsMap := map[string]any{}
extensionUris := []string{}
for key, asset := range assets {
assetMap := map[string]any{}
decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
TagName: "json",
Result: &assetMap,
})
if err != nil {
return nil, nil, err
}
if err := decoder.Decode(asset); err != nil {
return nil, nil, err
}
for _, extension := range asset.Extensions {
extensionUris = append(extensionUris, extension.URI())
if err := extension.Encode(assetMap); err != nil {
return nil, nil, err
}
}
assetsMap[key] = assetMap
}

return json.Marshal(assetMap)
return assetsMap, extensionUris, nil
}
119 changes: 97 additions & 22 deletions asset_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,46 +11,121 @@ import (
)

func TestAssetMarshal(t *testing.T) {
asset := &stac.Asset{
Title: "An Image",
Href: "https://example.com/image.tif",
Type: "image/tiff",
Roles: []string{"data", "reflectance"},
item := &stac.Item{
Version: "1.0.0",
Id: "item-id",
Geometry: map[string]any{
"type": "Point",
"coordinates": []float64{0, 0},
},
Properties: map[string]any{
"test": "value",
},
Links: []*stac.Link{
{Href: "https://example.com/stac/item-id", Rel: "self"},
},
Assets: map[string]*stac.Asset{
"image": {
Title: "An Image",
Href: "https://example.com/image.tif",
Type: "image/tiff",
Roles: []string{"data", "reflectance"},
},
},
}

data, err := json.Marshal(asset)
data, err := json.Marshal(item)
require.Nil(t, err)

expected := `{
"title": "An Image",
"href": "https://example.com/image.tif",
"type": "image/tiff",
"roles": ["data", "reflectance"]
"type": "Feature",
"stac_version": "1.0.0",
"id": "item-id",
"geometry": {
"type": "Point",
"coordinates": [0, 0]
},
"properties": {
"test": "value"
},
"links": [
{
"rel": "self",
"href": "https://example.com/stac/item-id"
}
],
"assets": {
"image": {
"title": "An Image",
"href": "https://example.com/image.tif",
"type": "image/tiff",
"roles": ["data", "reflectance"]
}
}
}`

assert.JSONEq(t, expected, string(data))
}

func TestAssetExtendedMarshal(t *testing.T) {
asset := &stac.Asset{
Href: "https://example.com/image.tif",
Type: "image/tiff",
Extensions: []stac.AssetExtension{
&pl.Asset{
AssetType: "ortho_analytic_4b_sr",
BundleType: "analytic_sr_udm2",
item := &stac.Item{
Version: "1.0.0",
Id: "item-id",
Geometry: map[string]any{
"type": "Point",
"coordinates": []float64{0, 0},
},
Properties: map[string]any{
"test": "value",
},
Links: []*stac.Link{
{Href: "https://example.com/stac/item-id", Rel: "self"},
},
Assets: map[string]*stac.Asset{
"extended": {
Href: "https://example.com/image.tif",
Type: "image/tiff",
Extensions: []stac.Extension{
&pl.Asset{
AssetType: "ortho_analytic_4b_sr",
BundleType: "analytic_sr_udm2",
},
},
},
},
}

data, err := json.Marshal(asset)
data, err := json.Marshal(item)
require.Nil(t, err)

expected := `{
"href": "https://example.com/image.tif",
"type": "image/tiff",
"pl:asset_type": "ortho_analytic_4b_sr",
"pl:bundle_type": "analytic_sr_udm2"
"type": "Feature",
"stac_version": "1.0.0",
"id": "item-id",
"geometry": {
"type": "Point",
"coordinates": [0, 0]
},
"properties": {
"test": "value"
},
"links": [
{
"rel": "self",
"href": "https://example.com/stac/item-id"
}
],
"assets": {
"extended": {
"href": "https://example.com/image.tif",
"type": "image/tiff",
"pl:asset_type": "ortho_analytic_4b_sr",
"pl:bundle_type": "analytic_sr_udm2"
}
},
"stac_extensions": [
"https://planetlabs.github.io/stac-extension/v1.0.0-beta.1/schema.json"
]
}`

assert.JSONEq(t, expected, string(data))
Expand Down
77 changes: 75 additions & 2 deletions collection.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package stac

import (
"encoding/json"
"fmt"
"regexp"

"github.com/mitchellh/mapstructure"
)
Expand All @@ -18,6 +20,22 @@ type Collection struct {
Summaries map[string]any `json:"summaries,omitempty"`
Links []*Link `json:"links"`
Assets map[string]*Asset `json:"assets,omitempty"`
Extensions []Extension `json:"-"`
}

var (
_ json.Marshaler = (*Collection)(nil)
_ json.Unmarshaler = (*Collection)(nil)
)

var collectionExtensions = newExtensionRegistry()

func RegisterCollectionExtension(pattern *regexp.Regexp, provider ExtensionProvider) {
collectionExtensions.register(pattern, provider)
}

func GetCollectionExtension(uri string) Extension {
return collectionExtensions.get(uri)
}

type Provider struct {
Expand All @@ -40,8 +58,6 @@ type TemporalExtent struct {
Interval [][]any `json:"interval"`
}

var _ json.Marshaler = (*Collection)(nil)

func (collection Collection) MarshalJSON() ([]byte, error) {
collectionMap := map[string]any{
"type": "Collection",
Expand All @@ -59,9 +75,66 @@ func (collection Collection) MarshalJSON() ([]byte, error) {
return nil, decodeErr
}

extensionUris := []string{}
lookup := map[string]bool{}

for _, extension := range collection.Extensions {
if err := extension.Encode(collectionMap); err != nil {
return nil, err
}
uris, err := GetExtensionUris(collectionMap)
if err != nil {
return nil, err
}
uris = append(uris, extension.URI())
for _, uri := range uris {
if !lookup[uri] {
extensionUris = append(extensionUris, uri)
lookup[uri] = true
}
}
}

SetExtensionUris(collectionMap, extensionUris)
return json.Marshal(collectionMap)
}

func (collection *Collection) UnmarshalJSON(data []byte) error {
collectionMap := map[string]any{}
if err := json.Unmarshal(data, &collectionMap); err != nil {
return err
}

decoder, decoderErr := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
TagName: "json",
Result: collection,
})
if decoderErr != nil {
return decoderErr
}

if err := decoder.Decode(collectionMap); err != nil {
return err
}

extensionUris, extensionErr := GetExtensionUris(collectionMap)
if extensionErr != nil {
return extensionErr
}
for _, uri := range extensionUris {
extension := GetCollectionExtension(uri)
if extension == nil {
continue
}
if err := extension.Decode(collectionMap); err != nil {
return fmt.Errorf("decoding error for %s: %w", uri, err)
}
collection.Extensions = append(collection.Extensions, extension)
}

return nil
}

type CollectionsList struct {
Collections []*Collection `json:"collections"`
Links []*Link `json:"links"`
Expand Down
Loading

0 comments on commit c65a270

Please sign in to comment.