Skip to content

Commit

Permalink
Merge pull request #243 from PDOK/refactoring
Browse files Browse the repository at this point in the history
refactor(go): polishing
  • Loading branch information
rkettelerij authored Sep 26, 2024
2 parents 237af17 + 06460e6 commit b1ec680
Show file tree
Hide file tree
Showing 9 changed files with 194 additions and 138 deletions.
10 changes: 10 additions & 0 deletions config/ogcapi_tiles.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,15 @@ func (o *OgcAPITiles) HasType(t TilesType) bool {
return false
}

// AllTileProjections projections supported by GoKoala for serving (vector) tiles, regardless of the dataset.
// When adding a new projection also add corresponding HTML/JSON templates.
var AllTileProjections = map[string]string{
"EPSG:28992": "NetherlandsRDNewQuad",
"EPSG:3035": "EuropeanETRS89_LAEAQuad",
"EPSG:3857": "WebMercatorQuad",
}

// HasProjection true when the given projection is supported for this dataset
func (o *OgcAPITiles) HasProjection(srs string) bool {
for _, projection := range o.GetProjections() {
if projection.Srs == srs {
Expand All @@ -54,6 +63,7 @@ func (o *OgcAPITiles) HasProjection(srs string) bool {
return false
}

// GetProjections projections supported for this dataset
func (o *OgcAPITiles) GetProjections() []SupportedSrs {
supportedSrsSet := map[SupportedSrs]struct{}{}
if o.DatasetTiles != nil {
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ require (
github.com/perimeterx/marshmallow v1.1.5 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
golang.org/x/crypto v0.24.0 // indirect
golang.org/x/net v0.26.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
Expand Down
35 changes: 35 additions & 0 deletions internal/engine/resources.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package engine

import (
"log"
"net/http"
"net/url"
"strings"

"github.com/go-chi/chi/v5"
)

// Serve static assets either from local storage or through reverse proxy
func newResourcesEndpoint(e *Engine) {
if e.Config.Resources.Directory != nil && *e.Config.Resources.Directory != "" {
resourcesPath := strings.TrimSuffix(*e.Config.Resources.Directory, "/resources")
e.Router.Handle("/resources/*", http.FileServer(http.Dir(resourcesPath)))
} else if e.Config.Resources.URL != nil && e.Config.Resources.URL.String() != "" {
e.Router.Get("/resources/*", proxy(e.ReverseProxy, e.Config.Resources.URL.String()))
}
}

type reverseProxy func(w http.ResponseWriter, r *http.Request, target *url.URL, prefer204 bool, overwrite string)

func proxy(rp reverseProxy, resourcesURL string) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
resourcePath, _ := url.JoinPath("/", chi.URLParam(r, "*"))
target, err := url.ParseRequestURI(resourcesURL + resourcePath)
if err != nil {
log.Printf("invalid target url, can't proxy resources: %v", err)
RenderProblem(ProblemServerError, w)
return
}
rp(w, r, target, true, "")
}
}
39 changes: 0 additions & 39 deletions internal/engine/resources_endpoint.go

This file was deleted.

77 changes: 77 additions & 0 deletions internal/engine/resources_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package engine

import (
"log"
"net/http"
"net/http/httptest"
"net/url"
"strings"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)

type MockReverseProxy struct {
mock.Mock
}

func (m *MockReverseProxy) Proxy(w http.ResponseWriter, r *http.Request, target *url.URL, prefer204 bool, overwrite string) {
m.Called(w, r, target, prefer204, overwrite)
}

func TestProxy(t *testing.T) {
tests := []struct {
name string
resourcesURL string
urlParam string
expectedStatus int
expectedLog string
expectProxy bool
}{
{
name: "valid url",
resourcesURL: "http://example.com/resources",
urlParam: "file",
expectedStatus: http.StatusOK,
expectedLog: "",
expectProxy: true,
},
{
name: "invalid url",
resourcesURL: "foo bar",
urlParam: "file",
expectedStatus: http.StatusInternalServerError,
expectedLog: "invalid target url, can't proxy resources",
expectProxy: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// given
mockReverseProxy := MockReverseProxy{}
if tt.expectProxy {
mockReverseProxy.On("Proxy", mock.Anything, mock.Anything, mock.Anything, true, "").Return()
}
r := httptest.NewRequest(http.MethodGet, "/resources/"+tt.urlParam, nil)
w := httptest.NewRecorder()
var logOutput strings.Builder
log.SetOutput(&logOutput)

// when
proxyHandler := proxy(mockReverseProxy.Proxy, tt.resourcesURL)
proxyHandler(w, r)

// then
assert.Equal(t, tt.expectedStatus, w.Result().StatusCode)
if tt.expectedLog != "" {
assert.Contains(t, logOutput.String(), tt.expectedLog)
}
if tt.expectProxy {
mockReverseProxy.AssertCalled(t, "Proxy", w, r, mock.Anything, true, "")
} else {
mockReverseProxy.AssertNotCalled(t, "Proxy", w, r, mock.Anything, true, "")
}
})
}
}
30 changes: 0 additions & 30 deletions internal/ogc/features/domain/props_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -241,33 +241,3 @@ func TestSetRelation(t *testing.T) {
})
}
}

func TestKeys(t *testing.T) {
tests := []struct {
name string
inOrder bool
data map[string]any
expected []string
}{
{
name: "Unordered keys",
inOrder: false,
data: map[string]any{"key2": "value2", "key1": "value1"},
expected: []string{"key1", "key2"}, // sorted alphabetically
},
{
name: "Ordered keys",
inOrder: true,
data: map[string]any{"key1": "value1", "key2": "value2"},
expected: []string{"key1", "key2"}, // insertion order maintained
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
p := NewFeaturePropertiesWithData(tt.inOrder, tt.data)
keys := p.Keys()
assert.Equal(t, tt.expected, keys)
})
}
}
122 changes: 65 additions & 57 deletions internal/ogc/styles/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ import (
"github.com/PDOK/gokoala/config"
"github.com/PDOK/gokoala/internal/engine"
"github.com/PDOK/gokoala/internal/engine/util"
"github.com/PDOK/gokoala/internal/ogc/tiles"

"github.com/go-chi/chi/v5"
)

Expand Down Expand Up @@ -62,72 +60,20 @@ func NewStyles(e *engine.Engine) *Styles {
e.Config.OgcAPI.Styles.SupportedStyles[0].ID, e.Config.OgcAPI.Styles.Default)
}

allProjections := util.Cast(tiles.AllProjections)
allProjections := util.Cast(config.AllTileProjections)
supportedProjections := e.Config.OgcAPI.Tiles.GetProjections()
if len(supportedProjections) == 0 {
log.Fatalf("failed to setup OGC API Styles, no supported projections (SRS) found in OGC API Tiles")
}
defaultProjection = strings.ToLower(tiles.AllProjections[supportedProjections[0].Srs])
defaultProjection = strings.ToLower(config.AllTileProjections[supportedProjections[0].Srs])

e.RenderTemplatesWithParams(stylesPath,
&stylesTemplateData{defaultProjection, supportedProjections, allProjections},
stylesBreadcrumbs,
engine.NewTemplateKey(templatesDir+"styles.go.json"),
engine.NewTemplateKey(templatesDir+"styles.go.html"))

for _, style := range e.Config.OgcAPI.Styles.SupportedStyles {
for _, supportedSrs := range supportedProjections {
projection := tiles.AllProjections[supportedSrs.Srs]
zoomLevelRange := supportedSrs.ZoomLevelRange
styleInstanceID := style.ID + projectionDelimiter + strings.ToLower(projection)
styleProjectionBreadcrumb := engine.Breadcrumb{
Name: style.Title + " (" + projection + ")",
Path: stylesCrumb + styleInstanceID,
}
data := &stylesMetadataTemplateData{style, projection}

// Render metadata template (JSON)
path := stylesPath + "/" + styleInstanceID + "/metadata"
e.RenderTemplatesWithParams(path, data, nil,
engine.NewTemplateKeyWithName(templatesDir+"styleMetadata.go.json", styleInstanceID))

// Render metadata template (HTML)
styleMetadataBreadcrumbs := stylesBreadcrumbs
styleMetadataBreadcrumbs = append(styleMetadataBreadcrumbs, []engine.Breadcrumb{
styleProjectionBreadcrumb,
{
Name: "Metadata",
Path: stylesCrumb + styleInstanceID + "/metadata",
},
}...)
e.RenderTemplatesWithParams(path, data, styleMetadataBreadcrumbs,
engine.NewTemplateKeyWithName(templatesDir+"styleMetadata.go.html", styleInstanceID))

// Add existing style definitions to rendered templates
for _, styleFormat := range style.Formats {
formatExtension := e.CN.GetStyleFormatExtension(styleFormat.Format)
styleKey := engine.TemplateKey{
Name: style.ID + formatExtension,
Directory: e.Config.OgcAPI.Styles.StylesDir,
Format: styleFormat.Format,
InstanceName: styleInstanceID + "." + styleFormat.Format,
}
path = stylesPath + "/" + styleInstanceID

// Render template (JSON)
e.RenderTemplatesWithParams(path, struct {
Projection string
ZoomLevelRange config.ZoomLevelRange
}{Projection: projection, ZoomLevelRange: zoomLevelRange}, nil, styleKey)

// Render template (HTML)
styleBreadCrumbs := stylesBreadcrumbs
styleBreadCrumbs = append(styleBreadCrumbs, styleProjectionBreadcrumb)
e.RenderTemplatesWithParams(path, style, styleBreadCrumbs,
engine.NewTemplateKeyWithName(templatesDir+"style.go.html", styleInstanceID))
}
}
}
renderStylesPerProjection(e, supportedProjections)

styles := &Styles{
engine: e,
Expand Down Expand Up @@ -198,3 +144,65 @@ func (s *Styles) StyleMetadata() http.HandlerFunc {
s.engine.ServePage(w, r, key)
}
}

func renderStylesPerProjection(e *engine.Engine, supportedProjections []config.SupportedSrs) {
for _, style := range e.Config.OgcAPI.Styles.SupportedStyles {
for _, supportedSrs := range supportedProjections {
projection := config.AllTileProjections[supportedSrs.Srs]
zoomLevelRange := supportedSrs.ZoomLevelRange
styleInstanceID := style.ID + projectionDelimiter + strings.ToLower(projection)
styleProjectionBreadcrumb := engine.Breadcrumb{
Name: style.Title + " (" + projection + ")",
Path: stylesCrumb + styleInstanceID,
}
data := &stylesMetadataTemplateData{style, projection}

// Render metadata template (JSON)
path := stylesPath + "/" + styleInstanceID + "/metadata"
e.RenderTemplatesWithParams(path, data, nil,
engine.NewTemplateKeyWithName(templatesDir+"styleMetadata.go.json", styleInstanceID))

// Render metadata template (HTML)
styleMetadataBreadcrumbs := stylesBreadcrumbs
styleMetadataBreadcrumbs = append(styleMetadataBreadcrumbs, []engine.Breadcrumb{
styleProjectionBreadcrumb,
{
Name: "Metadata",
Path: stylesCrumb + styleInstanceID + "/metadata",
},
}...)
e.RenderTemplatesWithParams(path, data, styleMetadataBreadcrumbs,
engine.NewTemplateKeyWithName(templatesDir+"styleMetadata.go.html", styleInstanceID))

// Add existing style definitions to rendered templates
renderStylePerFormat(e, style, styleInstanceID, projection, zoomLevelRange, styleProjectionBreadcrumb)
}
}
}

func renderStylePerFormat(e *engine.Engine, style config.Style, styleInstanceID string,
projection string, zoomLevelRange config.ZoomLevelRange, styleProjectionBreadcrumb engine.Breadcrumb) {

for _, styleFormat := range style.Formats {
formatExtension := e.CN.GetStyleFormatExtension(styleFormat.Format)
styleKey := engine.TemplateKey{
Name: style.ID + formatExtension,
Directory: e.Config.OgcAPI.Styles.StylesDir,
Format: styleFormat.Format,
InstanceName: styleInstanceID + "." + styleFormat.Format,
}
path := stylesPath + "/" + styleInstanceID

// Render template (JSON)
e.RenderTemplatesWithParams(path, struct {
Projection string
ZoomLevelRange config.ZoomLevelRange
}{Projection: projection, ZoomLevelRange: zoomLevelRange}, nil, styleKey)

// Render template (HTML)
styleBreadCrumbs := stylesBreadcrumbs
styleBreadCrumbs = append(styleBreadCrumbs, styleProjectionBreadcrumb)
e.RenderTemplatesWithParams(path, style, styleBreadCrumbs,
engine.NewTemplateKeyWithName(templatesDir+"style.go.html", styleInstanceID))
}
}
Loading

0 comments on commit b1ec680

Please sign in to comment.