Skip to content

Commit 2444013

Browse files
Merge pull request #179 from planetlabs/add-catalog-extensions
add extension support to Catalog scope
2 parents d88c72d + 2107d9a commit 2444013

File tree

2 files changed

+215
-7
lines changed

2 files changed

+215
-7
lines changed

catalog.go

+81-7
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,36 @@ package stac
22

33
import (
44
"encoding/json"
5+
"fmt"
6+
"regexp"
57

68
"github.com/mitchellh/mapstructure"
79
)
810

911
type Catalog struct {
10-
Version string `json:"stac_version"`
11-
Id string `json:"id"`
12-
Title string `json:"title,omitempty"`
13-
Description string `json:"description"`
14-
Links []*Link `json:"links"`
15-
ConformsTo []string `json:"conformsTo,omitempty"`
12+
Version string `json:"stac_version"`
13+
Id string `json:"id"`
14+
Title string `json:"title,omitempty"`
15+
Description string `json:"description"`
16+
Links []*Link `json:"links"`
17+
ConformsTo []string `json:"conformsTo,omitempty"`
18+
Extensions []Extension `json:"-"`
1619
}
1720

18-
var _ json.Marshaler = (*Catalog)(nil)
21+
var (
22+
_ json.Marshaler = (*Catalog)(nil)
23+
_ json.Unmarshaler = (*Catalog)(nil)
24+
)
25+
26+
var catalogExtensions = newExtensionRegistry()
27+
28+
func RegisterCatalogExtension(pattern *regexp.Regexp, provider ExtensionProvider) {
29+
catalogExtensions.register(pattern, provider)
30+
}
31+
32+
func GetCatalogExtension(uri string) Extension {
33+
return catalogExtensions.get(uri)
34+
}
1935

2036
func (catalog Catalog) MarshalJSON() ([]byte, error) {
2137
collectionMap := map[string]any{
@@ -34,5 +50,63 @@ func (catalog Catalog) MarshalJSON() ([]byte, error) {
3450
return nil, decodeErr
3551
}
3652

53+
extensionUris := []string{}
54+
lookup := map[string]bool{}
55+
56+
for _, extension := range catalog.Extensions {
57+
if err := extension.Encode(collectionMap); err != nil {
58+
return nil, err
59+
}
60+
uris, err := GetExtensionUris(collectionMap)
61+
if err != nil {
62+
return nil, err
63+
}
64+
uris = append(uris, extension.URI())
65+
for _, uri := range uris {
66+
if !lookup[uri] {
67+
extensionUris = append(extensionUris, uri)
68+
lookup[uri] = true
69+
}
70+
}
71+
}
72+
73+
SetExtensionUris(collectionMap, extensionUris)
3774
return json.Marshal(collectionMap)
3875
}
76+
77+
func (catalog *Catalog) UnmarshalJSON(data []byte) error {
78+
collectionMap := map[string]any{}
79+
if err := json.Unmarshal(data, &collectionMap); err != nil {
80+
return err
81+
}
82+
83+
decoder, decoderErr := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
84+
TagName: "json",
85+
Result: catalog,
86+
})
87+
if decoderErr != nil {
88+
return decoderErr
89+
}
90+
91+
if err := decoder.Decode(collectionMap); err != nil {
92+
return err
93+
}
94+
95+
extensionUris, err := GetExtensionUris(collectionMap)
96+
if err != nil {
97+
return err
98+
}
99+
100+
for _, uri := range extensionUris {
101+
extension := GetCatalogExtension(uri)
102+
if extension == nil {
103+
continue
104+
}
105+
if err := extension.Decode(collectionMap); err != nil {
106+
return fmt.Errorf("decoding error for %s: %w", uri, err)
107+
}
108+
catalog.Extensions = append(catalog.Extensions, extension)
109+
}
110+
111+
return nil
112+
}

catalog_test.go

+134
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ package stac_test
22

33
import (
44
"encoding/json"
5+
"regexp"
56
"testing"
67

8+
"github.com/mitchellh/mapstructure"
79
"github.com/planetlabs/go-stac"
810
"github.com/stretchr/testify/assert"
911
"github.com/stretchr/testify/require"
@@ -38,3 +40,135 @@ func TestCatalogMarshal(t *testing.T) {
3840

3941
assert.JSONEq(t, expected, string(data))
4042
}
43+
44+
const (
45+
extensionAlias = "test-catalog-extension"
46+
extensionUri = "https://example.com/test-catalog-extension/v1.0.0/schema.json"
47+
extensionPattern = `https://example.com/test-catalog-extension/v1\..*/schema.json`
48+
)
49+
50+
type CatalogExtension struct {
51+
RequiredNum float64 `json:"required_num"`
52+
OptionalBool *bool `json:"optional_bool,omitempty"`
53+
}
54+
55+
var _ stac.Extension = (*CatalogExtension)(nil)
56+
57+
func (*CatalogExtension) URI() string {
58+
return extensionUri
59+
}
60+
61+
func (e *CatalogExtension) Encode(catalogMap map[string]any) error {
62+
extendedProps := map[string]any{}
63+
encoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
64+
TagName: "json",
65+
Result: &extendedProps,
66+
})
67+
if err != nil {
68+
return err
69+
}
70+
if err := encoder.Decode(e); err != nil {
71+
return err
72+
}
73+
catalogMap[extensionAlias] = extendedProps
74+
return nil
75+
}
76+
77+
func (e *CatalogExtension) Decode(catalogMap map[string]any) error {
78+
extendedProps, present := catalogMap[extensionAlias]
79+
if !present {
80+
return nil
81+
}
82+
83+
decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
84+
TagName: "json",
85+
Result: e,
86+
})
87+
if err != nil {
88+
return err
89+
}
90+
return decoder.Decode(extendedProps)
91+
}
92+
93+
func TestExtendedCatalogMarshal(t *testing.T) {
94+
stac.RegisterCatalogExtension(
95+
regexp.MustCompile(extensionPattern),
96+
func() stac.Extension {
97+
return &CatalogExtension{}
98+
},
99+
)
100+
101+
catalog := &stac.Catalog{
102+
Description: "Test catalog with extension",
103+
Id: "catalog-id",
104+
Extensions: []stac.Extension{
105+
&CatalogExtension{
106+
RequiredNum: 42,
107+
},
108+
},
109+
Links: []*stac.Link{},
110+
Version: "1.2.3",
111+
}
112+
113+
data, err := json.Marshal(catalog)
114+
require.NoError(t, err)
115+
116+
expected := `{
117+
"type": "Catalog",
118+
"description": "Test catalog with extension",
119+
"id": "catalog-id",
120+
"test-catalog-extension": {
121+
"required_num": 42
122+
},
123+
"links": [],
124+
"stac_extensions": [
125+
"https://example.com/test-catalog-extension/v1.0.0/schema.json"
126+
],
127+
"stac_version": "1.2.3"
128+
}`
129+
130+
assert.JSONEq(t, expected, string(data))
131+
}
132+
133+
func TestExtendedCatalogUnmarshal(t *testing.T) {
134+
stac.RegisterCatalogExtension(
135+
regexp.MustCompile(extensionPattern),
136+
func() stac.Extension {
137+
return &CatalogExtension{}
138+
},
139+
)
140+
141+
data := []byte(`{
142+
"type": "Catalog",
143+
"description": "Test catalog with extension",
144+
"id": "catalog-id",
145+
"test-catalog-extension": {
146+
"required_num": 100,
147+
"optional_bool": true
148+
},
149+
"links": [],
150+
"stac_extensions": [
151+
"https://example.com/test-catalog-extension/v1.0.0/schema.json"
152+
],
153+
"stac_version": "1.2.3"
154+
}`)
155+
156+
catalog := &stac.Catalog{}
157+
require.NoError(t, json.Unmarshal(data, catalog))
158+
159+
b := true
160+
expected := &stac.Catalog{
161+
Description: "Test catalog with extension",
162+
Id: "catalog-id",
163+
Extensions: []stac.Extension{
164+
&CatalogExtension{
165+
RequiredNum: 100,
166+
OptionalBool: &b,
167+
},
168+
},
169+
Links: []*stac.Link{},
170+
Version: "1.2.3",
171+
}
172+
173+
assert.Equal(t, expected, catalog)
174+
}

0 commit comments

Comments
 (0)