diff --git a/Dockerfile b/Dockerfile index 55d0f89d..124ff2e6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,6 +3,7 @@ FROM docker.io/node:lts-alpine3.17 AS build-component RUN mkdir -p /usr/src/app COPY ./viewer /usr/src/app WORKDIR /usr/src/app +RUN npm config set registry http://registry.npmjs.org RUN npm install RUN npm run build diff --git a/cmd/main_test.go b/cmd/main_test.go index 075396d0..6ed6700b 100644 --- a/cmd/main_test.go +++ b/cmd/main_test.go @@ -47,19 +47,19 @@ func Test_newRouter(t *testing.T) { { name: "Serve multiple OGC APIs for single collection in JSON", configFile: "internal/engine/testdata/config_multiple_ogc_apis_single_collection.yaml", - apiCall: "http://localhost:8180/collections/NewYork?f=json", + apiCall: "http://localhost:8180/collections/newyork?f=json", wantBody: "internal/engine/testdata/expected_multiple_ogc_apis_single_collection.json", }, { name: "Serve multiple OGC APIs for single collection in HTML", configFile: "internal/engine/testdata/config_multiple_ogc_apis_single_collection.yaml", - apiCall: "http://localhost:8180/collections/NewYork?f=html", + apiCall: "http://localhost:8180/collections/newyork?f=html", wantBody: "internal/engine/testdata/expected_multiple_ogc_apis_single_collection.html", }, { name: "Serve JSON-LD in multiple OGC APIs for single collection in HTML", configFile: "internal/engine/testdata/config_multiple_ogc_apis_single_collection.yaml", - apiCall: "http://localhost:8180/collections/NewYork?f=html", + apiCall: "http://localhost:8180/collections/newyork?f=html", wantBody: "internal/engine/testdata/expected_multiple_ogc_apis_single_collection_json_ld.html", }, { diff --git a/config/collections.go b/config/collections.go index 72ec798c..0c54c2e3 100644 --- a/config/collections.go +++ b/config/collections.go @@ -15,7 +15,8 @@ type GeoSpatialCollections []GeoSpatialCollection // +kubebuilder:object:generate=true type GeoSpatialCollection struct { // Unique ID of the collection - ID string `yaml:"id" validate:"required" json:"id"` + // +kubebuilder:validation:Pattern=`^[a-z0-9]([a-z0-9_-]*[a-z0-9]+|)$` + ID string `yaml:"id" validate:"required,lowercase_id" json:"id"` // Metadata describing the collection contents // +optional diff --git a/config/config.go b/config/config.go index 4d12f9b9..4ed4ebfd 100644 --- a/config/config.go +++ b/config/config.go @@ -255,7 +255,11 @@ func setHealthCheckTilePath(tilesConfig *Tiles) { func validate(config *Config) error { // process 'validate' tags v := validator.New() - err := v.Struct(config) + err := RegisterAllValidators(v) + if err != nil { + return err + } + err = v.Struct(config) if err != nil { var ive *validator.InvalidValidationError if ok := errors.Is(err, ive); ok { diff --git a/config/config_test.go b/config/config_test.go index aef7495f..fac78e1d 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -81,28 +81,28 @@ func TestGeoSpatialCollections_Ordering(t *testing.T) { name: "should return collections in default order (alphabetic)", args: args{ configFile: "internal/engine/testdata/config_collections_order_alphabetic.yaml", - expectedOrder: []string{"A", "B", "C", "Z", "Z"}, + expectedOrder: []string{"a", "b", "c", "z", "z"}, }, }, { name: "should return collections in default order (alphabetic) - by title", args: args{ configFile: "internal/engine/testdata/config_collections_order_alphabetic_titles.yaml", - expectedOrder: []string{"B", "C", "Z", "Z", "A"}, + expectedOrder: []string{"b", "c", "z", "z", "a"}, }, }, { name: "should return collections in default order (alphabetic) - extra test", args: args{ configFile: "internal/engine/testdata/config_collections_unique.yaml", - expectedOrder: []string{"BarCollection", "FooCollection", "FooCollection"}, + expectedOrder: []string{"bar_collection", "foo_collection", "foo_collection"}, }, }, { name: "should return collections in explicit / literal order", args: args{ configFile: "internal/engine/testdata/config_collections_order_literal.yaml", - expectedOrder: []string{"Z", "Z", "C", "A", "B"}, + expectedOrder: []string{"z", "z", "c", "a", "b"}, }, }, { diff --git a/config/ogcapi_styles.go b/config/ogcapi_styles.go index 8e7bc90a..9105f5eb 100644 --- a/config/ogcapi_styles.go +++ b/config/ogcapi_styles.go @@ -15,7 +15,8 @@ type OgcAPIStyles struct { // +kubebuilder:object:generate=true type Style struct { // Unique ID of this style - ID string `yaml:"id" json:"id" validate:"required"` + // +kubebuilder:validation:Pattern=`^[a-z0-9]([a-z0-9_-]*[a-z0-9]+|)$` + ID string `yaml:"id" json:"id" validate:"required,lowercase_id"` // Human-friendly name of this style Title string `yaml:"title" json:"title" validate:"required"` diff --git a/config/validators.go b/config/validators.go new file mode 100644 index 00000000..195ca354 --- /dev/null +++ b/config/validators.go @@ -0,0 +1,27 @@ +package config + +import ( + "regexp" + + "github.com/go-playground/validator/v10" +) + +var ( + lowercaseIDRegexp = regexp.MustCompile("^[a-z0-9]([a-z0-9_-]*[a-z0-9]+|)$") +) + +const ( + lowercaseID = "lowercase_id" +) + +func RegisterAllValidators(v *validator.Validate) error { + return v.RegisterValidation(lowercaseID, LowercaseID) +} + +// LowercaseID is the validation function for validating if the current field +// is not empty and contains only lowercase chars, numbers, hyphens or underscores. +// It's similar to RFC 1035 DNS label but not the same. +func LowercaseID(fl validator.FieldLevel) bool { + valAsString := fl.Field().String() + return lowercaseIDRegexp.MatchString(valAsString) +} diff --git a/examples/README.md b/examples/README.md index dbb60acd..9ff20209 100644 --- a/examples/README.md +++ b/examples/README.md @@ -32,7 +32,7 @@ This example uses 3D tiles of New York. - Start GoKoala as specified in the root [README](../README.md#run) and provide `config_3d.yaml` as the config file. - Open http://localhost:8080 to explore the landing page -- Call http://localhost:8080/collections/NewYork/3dtiles/6/0/1.b3dm to download a specific 3D tile +- Call http://localhost:8080/collections/newyork/3dtiles/6/0/1.b3dm to download a specific 3D tile ## OGC API All/Complete example diff --git a/examples/config_3d.yaml b/examples/config_3d.yaml index fa024e0c..96c07401 100644 --- a/examples/config_3d.yaml +++ b/examples/config_3d.yaml @@ -25,7 +25,7 @@ ogcApi: 3dgeovolumes: tileServer: https://maps.ecere.com/3DAPI/collections/ collections: - - id: NewYork + - id: newyork # optional basepath to 3D tiles on the tileserver. Defaults to the collection ID. tileServerPath: "NewYork/3DTiles" # URI template for individual 3D tiles diff --git a/internal/engine/testdata/config_collections_order_alphabetic.yaml b/internal/engine/testdata/config_collections_order_alphabetic.yaml index 1dd375c9..6421682c 100644 --- a/internal/engine/testdata/config_collections_order_alphabetic.yaml +++ b/internal/engine/testdata/config_collections_order_alphabetic.yaml @@ -12,12 +12,12 @@ ogcApi: 3dgeovolumes: tileServer: https://example.com collections: - - id: B - - id: Z - - id: C + - id: b + - id: z + - id: c tiles: collections: - - id: Z + - id: z tileServer: https://example.com - - id: A + - id: a tileServer: https://example.com diff --git a/internal/engine/testdata/config_collections_order_alphabetic_titles.yaml b/internal/engine/testdata/config_collections_order_alphabetic_titles.yaml index a7ef7168..9846587d 100644 --- a/internal/engine/testdata/config_collections_order_alphabetic_titles.yaml +++ b/internal/engine/testdata/config_collections_order_alphabetic_titles.yaml @@ -12,22 +12,22 @@ ogcApi: 3dgeovolumes: tileServer: https://example.com collections: - - id: B + - id: b metadata: title: Bear - - id: Z + - id: z metadata: title: Chicken - - id: C + - id: c metadata: title: Bird tiles: collections: - - id: Z + - id: z tileServer: https://example.com metadata: title: Chicken - - id: A + - id: a tileServer: https://example.com metadata: title: Horse diff --git a/internal/engine/testdata/config_collections_order_literal.yaml b/internal/engine/testdata/config_collections_order_literal.yaml index 958852fd..22181b53 100644 --- a/internal/engine/testdata/config_collections_order_literal.yaml +++ b/internal/engine/testdata/config_collections_order_literal.yaml @@ -9,20 +9,20 @@ license: url: https://creativecommons.org/publicdomain/zero/1.0/deed.nl baseUrl: http://localhost:8181 collectionOrder: - - Z - - C - - A - - B + - z + - c + - a + - b ogcApi: 3dgeovolumes: tileServer: https://example.com collections: - - id: B - - id: Z - - id: C + - id: b + - id: z + - id: c tiles: collections: - - id: Z + - id: z tileServer: https://example.com - - id: A + - id: a tileServer: https://example.com diff --git a/internal/engine/testdata/config_collections_unique.yaml b/internal/engine/testdata/config_collections_unique.yaml index 004b1368..3caa8f7f 100644 --- a/internal/engine/testdata/config_collections_unique.yaml +++ b/internal/engine/testdata/config_collections_unique.yaml @@ -12,10 +12,10 @@ ogcApi: 3dgeovolumes: tileServer: https://example.com collections: - - id: FooCollection + - id: foo_collection tiles: collections: - - id: BarCollection + - id: bar_collection tileServer: https://example.com - - id: FooCollection + - id: foo_collection tileServer: https://example.com diff --git a/internal/engine/testdata/config_multiple_ogc_apis_single_collection.yaml b/internal/engine/testdata/config_multiple_ogc_apis_single_collection.yaml index 65d1d66a..5793d454 100644 --- a/internal/engine/testdata/config_multiple_ogc_apis_single_collection.yaml +++ b/internal/engine/testdata/config_multiple_ogc_apis_single_collection.yaml @@ -30,7 +30,7 @@ ogcApi: 3dgeovolumes: tileServer: https://maps.ecere.com/3DAPI/collections/ collections: - - id: NewYork + - id: newyork # reference to common metadata metadata: *collectionMetadata tileServerPath: "NewYork/3DTiles" @@ -48,7 +48,7 @@ ogcApi: local: file: ./examples/resources/addresses-rd.gpkg collections: - - id: NewYork + - id: newyork tableName: addresses # reference to common metadata metadata: *collectionMetadata diff --git a/internal/engine/testdata/expected_multiple_ogc_apis_single_collection.html b/internal/engine/testdata/expected_multiple_ogc_apis_single_collection.html index 2fb03bdb..3a71e14a 100644 --- a/internal/engine/testdata/expected_multiple_ogc_apis_single_collection.html +++ b/internal/engine/testdata/expected_multiple_ogc_apis_single_collection.html @@ -5,7 +5,7 @@
-Blader door de Features of ga direct naar de features in: +Blader door de Features of ga direct naar de features in:
diff --git a/internal/engine/testdata/expected_multiple_ogc_apis_single_collection.json b/internal/engine/testdata/expected_multiple_ogc_apis_single_collection.json index 71cc9c11..3f832c99 100644 --- a/internal/engine/testdata/expected_multiple_ogc_apis_single_collection.json +++ b/internal/engine/testdata/expected_multiple_ogc_apis_single_collection.json @@ -1,6 +1,6 @@ { - "id": "NewYork", - "title": "NewYork", + "id": "newyork", + "title": "newyork", "description": "This is a description about the NewYork collection in Markdown. We offer both 3D Tiles and Features for this collection.", "keywords": [ { @@ -44,52 +44,52 @@ "type": "application/json", "title": "This document as JSON", "updated": "2023-05-10T12:00:00Z", - "href": "http://localhost:8180/collections/NewYork?f=json" + "href": "http://localhost:8180/collections/newyork?f=json" }, { "rel": "alternate", "type": "text/html", "title": "This document as HTML", "updated": "2023-05-10T12:00:00Z", - "href": "http://localhost:8180/collections/NewYork?f=html" + "href": "http://localhost:8180/collections/newyork?f=html" }, { "rel": "preview", "type": "image/png", - "title": "Thumbnail for NewYork", + "title": "Thumbnail for newyork", "href": "http://localhost:8180/resources/3d.png" }, { "rel": "items", "type": "application/json+3dtiles", - "title": "Tileset definition of collection NewYork according to the OGC 3D Tiles specification", - "href": "http://localhost:8180/collections/NewYork/3dtiles?f=json" + "title": "Tileset definition of collection newyork according to the OGC 3D Tiles specification", + "href": "http://localhost:8180/collections/newyork/3dtiles?f=json" }, { "rel": "items", "type": "application/geo+json", - "title": "The JSON representation of the NewYork features served from this endpoint", - "href": "http://localhost:8180/collections/NewYork/items?f=json" + "title": "The JSON representation of the newyork features served from this endpoint", + "href": "http://localhost:8180/collections/newyork/items?f=json" }, { "rel": "items", "type": "application/vnd.ogc.fg+json", - "title": "The JSON-FG representation of the NewYork features served from this endpoint", - "href": "http://localhost:8180/collections/NewYork/items?f=jsonfg" + "title": "The JSON-FG representation of the newyork features served from this endpoint", + "href": "http://localhost:8180/collections/newyork/items?f=jsonfg" }, { "rel": "items", "type": "text/html", - "title": "The HTML representation of the NewYork features served from this endpoint", - "href": "http://localhost:8180/collections/NewYork/items?f=html" + "title": "The HTML representation of the newyork features served from this endpoint", + "href": "http://localhost:8180/collections/newyork/items?f=html" } ], "content": [ { "rel": "original", "type": "application/json+3dtiles", - "title": "Tileset definition of collection NewYork according to the OGC 3D Tiles specification", - "href": "http://localhost:8180/collections/NewYork/3dtiles?f=json", + "title": "Tileset definition of collection newyork according to the OGC 3D Tiles specification", + "href": "http://localhost:8180/collections/newyork/3dtiles?f=json", "collectionType": "3d-container" } ] diff --git a/internal/engine/testdata/expected_multiple_ogc_apis_single_collection_json_ld.html b/internal/engine/testdata/expected_multiple_ogc_apis_single_collection_json_ld.html index 6a8efee1..eb1e4049 100644 --- a/internal/engine/testdata/expected_multiple_ogc_apis_single_collection_json_ld.html +++ b/internal/engine/testdata/expected_multiple_ogc_apis_single_collection_json_ld.html @@ -5,7 +5,7 @@ "isPartOf": "http:\/\/localhost:8180?f=html", "name": "New York - NewYork", "description": "This is a description about the NewYork collection in Markdown. We offer both 3D Tiles and Features for this collection.", - "url": "http:\/\/localhost:8180/collections/NewYork?f=html","keywords": ["Keyword1","Keyword2"],"license": "https:\/\/creativecommons.org\/publicdomain\/zero\/1.0\/deed.nl", + "url": "http:\/\/localhost:8180/collections/newyork?f=html","keywords": ["Keyword1","Keyword2"],"license": "https:\/\/creativecommons.org\/publicdomain\/zero\/1.0\/deed.nl", "isAccessibleForFree": true ,"thumbnailUrl": "http:\/\/localhost:8180/resources/3d.png" ,"version": "2023-05-10"