From 492adec2101b785e44be4bf604a41b28e23d2919 Mon Sep 17 00:00:00 2001 From: Wouter Date: Thu, 23 Jul 2020 14:08:21 +0200 Subject: [PATCH 01/45] init validation setup --- go.mod | 3 + go.sum | 17 +++++ pkg/wms130/request/getmap.go | 42 +++++++----- pkg/wms130/validation/translations.go | 50 ++++++++++++++ pkg/wms130/validation/validations.go | 46 +++++++++++++ pkg/wms130/validation/validator_test.go | 89 +++++++++++++++++++++++++ pkg/wms130/validation/wrappers.go | 12 ++++ 7 files changed, 243 insertions(+), 16 deletions(-) create mode 100644 pkg/wms130/validation/translations.go create mode 100644 pkg/wms130/validation/validations.go create mode 100644 pkg/wms130/validation/validator_test.go create mode 100644 pkg/wms130/validation/wrappers.go diff --git a/go.mod b/go.mod index 6f8de6a..64e8f21 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,9 @@ module github.com/pdok/ogc-specifications go 1.13 require ( + github.com/go-playground/locales v0.13.0 + github.com/go-playground/universal-translator v0.17.0 + github.com/go-playground/validator/v10 v10.3.0 github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99 // indirect github.com/ianlancetaylor/demangle v0.0.0-20200715173712-053cf528c12f // indirect golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae // indirect diff --git a/go.sum b/go.sum index 965922c..37d37c4 100644 --- a/go.sum +++ b/go.sum @@ -2,17 +2,34 @@ github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWR github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= +github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= +github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= +github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= +github.com/go-playground/validator v9.31.0+incompatible h1:UA72EPEogEnq76ehGdEDp4Mit+3FDh548oRqwVgNsHA= +github.com/go-playground/validator/v10 v10.3.0 h1:nZU+7q+yJoFmwvNgv/LnPUkwPal62+b2xXj0AU1Es7o= +github.com/go-playground/validator/v10 v10.3.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99 h1:Ak8CrdlwwXwAZxzS66vgPt4U8yUZX7JwLvVR58FN5jM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6 h1:UDMh68UUwekSh5iP2OMhRRZJiiBccgV7axzUG8vi56c= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200715173712-053cf528c12f h1:nTaA/z8mev5oJv1dNSbu8Pwvu5CJyBZKwDvHpr9FZ4I= github.com/ianlancetaylor/demangle v0.0.0-20200715173712-053cf528c12f/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= +github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e h1:9vRrk9YW2BTzLP0VCB9ZDjU4cPqkg+IDWL7XgxA1yxQ= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae h1:Ih9Yo4hSPImZOpfGuA4bR/ORKTAbhZo2AbWNRCnevdo= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/pkg/wms130/request/getmap.go b/pkg/wms130/request/getmap.go index 845dbd5..2df28cc 100644 --- a/pkg/wms130/request/getmap.go +++ b/pkg/wms130/request/getmap.go @@ -335,29 +335,39 @@ func buildStyledLayerDescriptor(layers, styles []string) (StyledLayerDescriptor, // TODO maybe 'merge' both func in a single one with 2 outputs // so their are 'insync' ...? func (sld *StyledLayerDescriptor) getLayerKVPValue() string { - queryvalue := "" - for p, l := range sld.NamedLayer { - queryvalue = queryvalue + l.Name - if p < len(sld.NamedLayer)-1 { - queryvalue = queryvalue + "," - } - } - return queryvalue + return strings.Join(sld.GetNamedLayers(), ",") } func (sld *StyledLayerDescriptor) getStyleKVPValue() string { - queryvalue := "" - for p, l := range sld.NamedLayer { + return strings.Join(sld.GetNamedStyles(), ",") +} + +// GetNamedLayers return an array of the Layer names +func (sld *StyledLayerDescriptor) GetNamedLayers() []string { + + layers := []string{} + for _, l := range sld.NamedLayer { + layers = append(layers, l.Name) + } + + return layers +} + +// GetNamedStyles return an array of the Layer names +func (sld *StyledLayerDescriptor) GetNamedStyles() []string { + + styles := []string{} + for _, l := range sld.NamedLayer { if l.Name != "" { if l.NamedStyle != nil { - queryvalue = queryvalue + l.NamedStyle.Name - } - if p < len(sld.NamedLayer)-1 { - queryvalue = queryvalue + "," + styles = append(styles, l.NamedStyle.Name) + } else { + styles = append(styles, "") } } } - return queryvalue + + return styles } // GetMap struct with the needed parameters/attributes needed for making a GetMap request @@ -391,7 +401,7 @@ type Size struct { // StyledLayerDescriptor struct type StyledLayerDescriptor struct { - Version string `xml:"version,attr" yaml:"version" validate:"required"` + Version string `xml:"version,attr" yaml:"version" validate:"required,eq=1.1.0"` NamedLayer []NamedLayer `xml:"NamedLayer" yaml:"namedlayer" validate:"required"` } diff --git a/pkg/wms130/validation/translations.go b/pkg/wms130/validation/translations.go new file mode 100644 index 0000000..99d34da --- /dev/null +++ b/pkg/wms130/validation/translations.go @@ -0,0 +1,50 @@ +package validation + +import ( + "fmt" + + ut "github.com/go-playground/universal-translator" + "github.com/go-playground/validator/v10" +) + +func registerTranslations(v *validator.Validate, trans *ut.Translator) { + + v.RegisterTranslation( + "epsg", + *trans, + func(ut ut.Translator) error { + return ut.Add("epsg", "{0} has a invalid value: {1}", true) + }, + func(ut ut.Translator, fe validator.FieldError) string { + t, _ := ut.T("epsg", fe.Field(), fmt.Sprintf("%v", fe.Value())) + return t + }, + ) + + v.RegisterTranslation("version", *trans, + func(ut ut.Translator) error { + return ut.Add("version", "{0} has a invalid value: {1}", true) + }, + func(ut ut.Translator, fe validator.FieldError) string { + t, _ := ut.T("version", fe.Field(), fmt.Sprintf("%v", fe.Value())) + return t + }) + + v.RegisterTranslation("required", *trans, + func(ut ut.Translator) error { + return ut.Add("required", "{0} is missing", true) + }, + func(ut ut.Translator, fe validator.FieldError) string { + t, _ := ut.T("required", fe.Field()) + return t + }) + + v.RegisterTranslation("knownlayer", *trans, + func(ut ut.Translator) error { + return ut.Add("knownlayer", "{0} has a invalid value {1}", true) + }, + func(ut ut.Translator, fe validator.FieldError) string { + t, _ := ut.T("knownlayer", fe.Field(), fmt.Sprintf("%v", fe.Value())) + return t + }) +} diff --git a/pkg/wms130/validation/validations.go b/pkg/wms130/validation/validations.go new file mode 100644 index 0000000..6b85f3a --- /dev/null +++ b/pkg/wms130/validation/validations.go @@ -0,0 +1,46 @@ +package validation + +import ( + "github.com/go-playground/validator/v10" + ows "github.com/pdok/ogc-specifications/pkg/ows" +) + +func registerValidations(v *validator.Validate) { + + v.RegisterStructValidation(BboxValidator, ows.BoundingBox{}) + v.RegisterStructValidation(GetMapValidation, GetMapWrapper{}) +} + +// BboxValidator implements validator.CustomTypeFunc +func BboxValidator(sl validator.StructLevel) { + + bbox := sl.Current().Interface().(ows.BoundingBox) + + if bbox.LowerCorner[0] >= bbox.UpperCorner[0] { + sl.ReportError(bbox, "lowerCorner", "LowerCorner", "bbox", `bbox`) + } + + if bbox.LowerCorner[1] >= bbox.UpperCorner[1] { + sl.ReportError(bbox, "lowerCorner", "LowerCorner", "bbox", `bbox`) + } + +} + +// GetMapValidation structlevel +func GetMapValidation(sl validator.StructLevel) { + + wr := sl.Current().Interface().(GetMapWrapper) + + // layer + for _, sldname := range wr.getmap.StyledLayerDescriptor.GetNamedLayers() { + known := false + for _, l := range wr.getcapabilities.Capability.Layer { + if *l.Name == sldname { + known = true + } + } + if !known { + sl.ReportError(wr.getmap.StyledLayerDescriptor.NamedLayer, "namedlayer", "NamedLayer", "knownlayer", sldname) + } + } +} diff --git a/pkg/wms130/validation/validator_test.go b/pkg/wms130/validation/validator_test.go new file mode 100644 index 0000000..16152a3 --- /dev/null +++ b/pkg/wms130/validation/validator_test.go @@ -0,0 +1,89 @@ +package validation + +import ( + "encoding/xml" + "testing" + + "github.com/go-playground/locales/en" + ut "github.com/go-playground/universal-translator" + "github.com/go-playground/validator/v10" + en_translations "github.com/go-playground/validator/v10/translations/en" + "github.com/pdok/ogc-specifications/pkg/ows" + "github.com/pdok/ogc-specifications/pkg/wms130/request" + "github.com/pdok/ogc-specifications/pkg/wms130/response" +) + +func sp(s string) *string { + return &s +} + +func TestValidation(t *testing.T) { + + en := en.New() + uni := ut.New(en, en) + trans, _ := uni.GetTranslator("en") + + v := validator.New() + en_translations.RegisterDefaultTranslations(v, trans) + + registerValidations(v) + registerTranslations(v, &trans) + + getcapabilities := response.GetCapabilities{ + WMSService: response.WMSService{Name: "RiversRoadsAndHouses"}, + Capability: response.Capability{ + Layer: []response.Layer{ + {Name: sp(`Rivers`), CRS: []*string{sp(`EPSG:4326`)}}, + {Name: sp(`Roads`), CRS: []*string{sp(`EPSG:4326`)}}, + {Name: sp(`Houses`), CRS: []*string{sp(`EPSG:4326`)}}, + }, + }, + } + + var tests = []struct { + Object request.GetMap + }{ + 0: { + Object: request.GetMap{ + XMLName: xml.Name{Local: "GetMap"}, + BaseRequest: request.BaseRequest{ + Version: "1.3.0", + Service: "WMS", + }, + StyledLayerDescriptor: request.StyledLayerDescriptor{ + Version: "1.1.0", + NamedLayer: []request.NamedLayer{ + {Name: "Rivers", NamedStyle: &request.NamedStyle{Name: "CenterLine"}}, + {Name: "Roads", NamedStyle: &request.NamedStyle{Name: "CenterLine"}}, + {Name: "Houses", NamedStyle: &request.NamedStyle{Name: "Outline"}}, + }}, + CRS: "EPSG:4326", + BoundingBox: ows.BoundingBox{ + LowerCorner: [2]float64{-180.0, -90.0}, + UpperCorner: [2]float64{180.0, 90.0}, + }, + Output: request.Output{ + Size: request.Size{Width: 1024, Height: 512}, + Format: "image/jpeg", + Transparent: sp("false")}, + Exceptions: sp("XML"), + }, + }, + } + + for k, n := range tests { + + wr := GetMapWrapper{getcapabilities: &getcapabilities, getmap: &n.Object} + + err := v.Struct(wr) + if err != nil { + t.Errorf("test: %d, got: %s", k, (err.(validator.ValidationErrors)).Translate(trans)) + } + + err = v.Struct(n.Object) + if err != nil { + + t.Errorf("test: %d, got: %s", k, (err.(validator.ValidationErrors)).Translate(trans)) + } + } +} diff --git a/pkg/wms130/validation/wrappers.go b/pkg/wms130/validation/wrappers.go new file mode 100644 index 0000000..e41e193 --- /dev/null +++ b/pkg/wms130/validation/wrappers.go @@ -0,0 +1,12 @@ +package validation + +import ( + "github.com/pdok/ogc-specifications/pkg/wms130/request" + "github.com/pdok/ogc-specifications/pkg/wms130/response" +) + +// GetMapWrapper struct +type GetMapWrapper struct { + getcapabilities *response.GetCapabilities + getmap *request.GetMap +} From 84128c02ba5e42794f528153b6cf39972e1a31d4 Mon Sep 17 00:00:00 2001 From: Wouter Date: Fri, 24 Jul 2020 09:13:46 +0200 Subject: [PATCH 02/45] fix I -> J, J -> I issue --- pkg/wms130/request/getfeatureinfo.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/pkg/wms130/request/getfeatureinfo.go b/pkg/wms130/request/getfeatureinfo.go index 688ab07..94e75ba 100644 --- a/pkg/wms130/request/getfeatureinfo.go +++ b/pkg/wms130/request/getfeatureinfo.go @@ -132,11 +132,11 @@ func (gfi *GetFeatureInfo) ParseKVP(query url.Values) ows.Exception { } gfi.I = i case J: - i, err := strconv.Atoi(query[k][0]) + j, err := strconv.Atoi(query[k][0]) if err != nil { return exception.InvalidPoint(query[I][0], query[J][0]) } - gfi.J = i + gfi.J = j } } } @@ -201,9 +201,9 @@ func (gfi *GetFeatureInfo) BuildKVP() url.Values { case QUERYLAYERS: querystring[QUERYLAYERS] = []string{strings.Join(gfi.QueryLayers, ",")} case I: - querystring[J] = []string{strconv.Itoa(gfi.I)} + querystring[I] = []string{strconv.Itoa(gfi.I)} case J: - querystring[I] = []string{strconv.Itoa(gfi.J)} + querystring[J] = []string{strconv.Itoa(gfi.J)} } } @@ -235,7 +235,6 @@ func (gfi *GetFeatureInfo) BuildXML() []byte { si, _ := xml.MarshalIndent(gfi, "", " ") re := regexp.MustCompile(`><.*>`) return []byte(xml.Header + re.ReplaceAllString(string(si), "/>")) - // return []byte(xml.Header + string(si)) } // GetFeatureInfo struct with the needed parameters/attributes needed for making a GetFeatureInfo request From 94bf28c9360f59f3fa0d2270512300f92157f613 Mon Sep 17 00:00:00 2001 From: Wouter Date: Mon, 27 Jul 2020 20:01:01 +0200 Subject: [PATCH 03/45] split capabilities from the response getcapabilities --- pkg/wcs201/capabilities/capabilities.go | 79 ++++++++++ pkg/wcs201/response/getcapabilities.go | 89 +---------- pkg/wfs200/capabilities/capabilities.go | 158 +++++++++++++++++++ pkg/wfs200/response/getcapabilities.go | 184 +++-------------------- pkg/wms130/capabilities/capabilities.go | 138 +++++++++++++++++ pkg/wms130/response/getcapabilities.go | 148 +----------------- pkg/wms130/validation/validator_test.go | 5 +- pkg/wmts100/capabilities/capabilities.go | 62 ++++++++ pkg/wmts100/response/getcapabilities.go | 64 +------- 9 files changed, 473 insertions(+), 454 deletions(-) create mode 100644 pkg/wcs201/capabilities/capabilities.go create mode 100644 pkg/wfs200/capabilities/capabilities.go create mode 100644 pkg/wms130/capabilities/capabilities.go create mode 100644 pkg/wmts100/capabilities/capabilities.go diff --git a/pkg/wcs201/capabilities/capabilities.go b/pkg/wcs201/capabilities/capabilities.go new file mode 100644 index 0000000..27e432e --- /dev/null +++ b/pkg/wcs201/capabilities/capabilities.go @@ -0,0 +1,79 @@ +package capabilities + +// OperationsMetadata struct for the WCS 2.0.1 +type OperationsMetadata struct { + Operation []Operation `xml:"ows:Operation" yaml:"operation"` + ExtendedCapabilities *ExtendedCapabilities `xml:"ows:ExtendedCapabilities" yaml:"extendedcapabilities"` +} + +// Operation in struct for repeatablity +type Operation struct { + Name string `xml:"name,attr" yaml:"name"` + DCP struct { + HTTP struct { + Get struct { + Type string `xml:"xlink:type,attr" yaml:"type"` + Href string `xml:"xlink:href,attr" yaml:"href"` + } `xml:"ows:Get" yaml:"get"` + Post *Post `xml:"ows:Post" yaml:"post"` + } `xml:"ows:HTTP" yaml:"http"` + } `xml:"ows:DCP" yaml:"dcp"` +} + +// Post in separated struct so to use it as a Pointer +type Post struct { + Type string `xml:"xlink:type,attr" yaml:"type"` + Href string `xml:"xlink:href,attr" yaml:"href"` + Constraint struct { + Name string `xml:"name,attr" yaml:"name"` + AllowedValues struct { + Value []string `xml:"ows:Value" yaml:"value"` + } `xml:"ows:AllowedValues" yaml:"allowedvalues"` + } `xml:"ows:Constraint" yaml:"constraint"` +} + +// ExtendedCapabilities struct for the WCS 2.0.1 +type ExtendedCapabilities struct { + ExtendedCapabilities struct { + MetadataURL struct { + Type string `xml:"xsi:type,attr"` + URL string `xml:"inspire_common:URL"` + MediaType string `xml:"inspire_common:MediaType"` + } `xml:"inspire_common:MetadataUrl"` + SupportedLanguages struct { + DefaultLanguage struct { + Language string `xml:"inspire_common:Language"` + } `xml:"inspire_common:DefaultLanguage"` + } `xml:"inspire_common:SupportedLanguages"` + ResponseLanguage struct { + Language string `xml:"inspire_common:Language"` + } `xml:"inspire_common:ResponseLanguage"` + SpatialDataSetIdentifier struct { + Code string `xml:"Code"` + } `xml:"inspire_dls:SpatialDataSetIdentifier"` + } `xml:"inspire_dls:ExtendedCapabilities"` +} + +// ServiceMetadata struct for the WCS 2.0.1 +type ServiceMetadata struct { + FormatSupported []string `xml:"wcs:formatSupported"` + Extension struct { + InterpolationMetadata struct { + InterpolationSupported []string `xml:"int:InterpolationSupported"` + } `xml:"int:InterpolationMetadata"` + CrsMetadata struct { + CrsSupported []string `xml:"crs:crsSupported"` + } `xml:"crs:CrsMetadata"` + } `xml:"wcs:Extension"` +} + +// Contents in struct for repeatablity +type Contents struct { + CoverageSummary []CoverageSummary `xml:"wcs:CoverageSummary"` +} + +// CoverageSummary in struct for repeatablity +type CoverageSummary struct { + CoverageID string `xml:"wcs:CoverageId"` + CoverageSubtype string `xml:"wcs:CoverageSubtype"` +} diff --git a/pkg/wcs201/response/getcapabilities.go b/pkg/wcs201/response/getcapabilities.go index 3ed71c9..dcef702 100644 --- a/pkg/wcs201/response/getcapabilities.go +++ b/pkg/wcs201/response/getcapabilities.go @@ -5,6 +5,7 @@ import ( "regexp" "github.com/pdok/ogc-specifications/pkg/ows" + "github.com/pdok/ogc-specifications/pkg/wcs201/capabilities" ) // @@ -51,11 +52,11 @@ func (gc *GetCapabilities) BuildXML() []byte { type GetCapabilities struct { XMLName xml.Name `xml:"wcs:Capabilities"` Namespaces `yaml:"namespaces"` - ServiceIdentification ServiceIdentification `xml:"ows:ServiceIdentification" yaml:"serviceidentification"` - ServiceProvider ServiceProvider `xml:"ows:ServiceProvider" yaml:"serviceprovider"` - OperationsMetadata OperationsMetadata `xml:"ows:OperationsMetadata" yaml:"operationsmetadata"` - ServiceMetadata ServiceMetadata `xml:"wcs:ServiceMetadata" yaml:"servicemetadata"` - Contents Contents `xml:"wcs:Contents" yaml:"contents"` + ServiceIdentification ServiceIdentification `xml:"ows:ServiceIdentification" yaml:"serviceidentification"` + ServiceProvider ServiceProvider `xml:"ows:ServiceProvider" yaml:"serviceprovider"` + OperationsMetadata capabilities.OperationsMetadata `xml:"ows:OperationsMetadata" yaml:"operationsmetadata"` + ServiceMetadata capabilities.ServiceMetadata `xml:"wcs:ServiceMetadata" yaml:"servicemetadata"` + Contents capabilities.Contents `xml:"wcs:Contents" yaml:"contents"` } // Namespaces struct containing the namespaces needed for the XML document @@ -126,81 +127,3 @@ type ServiceProvider struct { Role string `xml:"ows:Role" yaml:"role"` } `xml:"ows:ServiceContact" yaml:"servicecontact"` } - -// OperationsMetadata struct for the WCS 2.0.1 -type OperationsMetadata struct { - Operation []Operation `xml:"ows:Operation" yaml:"operation"` - ExtendedCapabilities *ExtendedCapabilities `xml:"ows:ExtendedCapabilities" yaml:"extendedcapabilities"` -} - -// Operation in struct for repeatablity -type Operation struct { - Name string `xml:"name,attr" yaml:"name"` - DCP struct { - HTTP struct { - Get struct { - Type string `xml:"xlink:type,attr" yaml:"type"` - Href string `xml:"xlink:href,attr" yaml:"href"` - } `xml:"ows:Get" yaml:"get"` - Post *Post `xml:"ows:Post" yaml:"post"` - } `xml:"ows:HTTP" yaml:"http"` - } `xml:"ows:DCP" yaml:"dcp"` -} - -// Post in separated struct so to use it as a Pointer -type Post struct { - Type string `xml:"xlink:type,attr" yaml:"type"` - Href string `xml:"xlink:href,attr" yaml:"href"` - Constraint struct { - Name string `xml:"name,attr" yaml:"name"` - AllowedValues struct { - Value []string `xml:"ows:Value" yaml:"value"` - } `xml:"ows:AllowedValues" yaml:"allowedvalues"` - } `xml:"ows:Constraint" yaml:"constraint"` -} - -// ExtendedCapabilities struct for the WCS 2.0.1 -type ExtendedCapabilities struct { - ExtendedCapabilities struct { - MetadataURL struct { - Type string `xml:"xsi:type,attr"` - URL string `xml:"inspire_common:URL"` - MediaType string `xml:"inspire_common:MediaType"` - } `xml:"inspire_common:MetadataUrl"` - SupportedLanguages struct { - DefaultLanguage struct { - Language string `xml:"inspire_common:Language"` - } `xml:"inspire_common:DefaultLanguage"` - } `xml:"inspire_common:SupportedLanguages"` - ResponseLanguage struct { - Language string `xml:"inspire_common:Language"` - } `xml:"inspire_common:ResponseLanguage"` - SpatialDataSetIdentifier struct { - Code string `xml:"Code"` - } `xml:"inspire_dls:SpatialDataSetIdentifier"` - } `xml:"inspire_dls:ExtendedCapabilities"` -} - -// ServiceMetadata struct for the WCS 2.0.1 -type ServiceMetadata struct { - FormatSupported []string `xml:"wcs:formatSupported"` - Extension struct { - InterpolationMetadata struct { - InterpolationSupported []string `xml:"int:InterpolationSupported"` - } `xml:"int:InterpolationMetadata"` - CrsMetadata struct { - CrsSupported []string `xml:"crs:crsSupported"` - } `xml:"crs:CrsMetadata"` - } `xml:"wcs:Extension"` -} - -// Contents in struct for repeatablity -type Contents struct { - CoverageSummary []CoverageSummary `xml:"wcs:CoverageSummary"` -} - -// CoverageSummary in struct for repeatablity -type CoverageSummary struct { - CoverageID string `xml:"wcs:CoverageId"` - CoverageSubtype string `xml:"wcs:CoverageSubtype"` -} diff --git a/pkg/wfs200/capabilities/capabilities.go b/pkg/wfs200/capabilities/capabilities.go new file mode 100644 index 0000000..a7967ef --- /dev/null +++ b/pkg/wfs200/capabilities/capabilities.go @@ -0,0 +1,158 @@ +package capabilities + +import "encoding/xml" + +// Method in separated struct so to use it as a Pointer +type Method struct { + Type string `xml:"xlink:type,attr" yaml:"type"` + Href string `xml:"xlink:href,attr" yaml:"href"` +} + +// OperationsMetadata struct for the WFS 2.0.0 +type OperationsMetadata struct { + XMLName xml.Name `xml:"ows:OperationsMetadata"` + Operation []Operation `xml:"ows:Operation"` + Parameter struct { + Name string `xml:"name,attr" yaml:"name"` + AllowedValues struct { + Value []string `xml:"ows:Value" yaml:"value"` + } `xml:"ows:AllowedValues" yaml:"allowedvalues"` + } `xml:"ows:Parameter" yaml:"parameter"` + Constraint []Constraint `xml:"ows:Constraint" yaml:"constraint"` + ExtendedCapabilities *ExtendedCapabilities `xml:"ows:ExtendedCapabilities" yaml:"extendedcapabilities"` +} + +// Constraint struct for the WFS 2.0.0 +type Constraint struct { + Text string `xml:",chardata"` + Name string `xml:"name,attr" yaml:"name"` + NoValues *string `xml:"ows:NoValues" yaml:"novalue"` + DefaultValue *string `xml:"ows:DefaultValue" yaml:"defaultvalue"` + AllowedValues *AllowedValues `xml:"ows:AllowedValues" yaml:"allowedvalues"` +} + +// Operation struct for the WFS 2.0.0 +type Operation struct { + Name string `xml:"name,attr"` + DCP struct { + HTTP struct { + Get *Method `xml:"ows:Get,omitempty" yaml:"get,omitempty"` + Post *Method `xml:"ows:Post,omitempty" yaml:"post,omitempty"` + } `xml:"ows:HTTP" yaml:"http"` + } `xml:"ows:DCP" yaml:"dcp"` + Parameter []struct { + Name string `xml:"name,attr"` + AllowedValues struct { + Value []string `xml:"ows:Value"` + } `xml:"ows:AllowedValues"` + } `xml:"ows:Parameter"` +} + +// AllowedValues struct so it can be used as a pointer +type AllowedValues struct { + Value []string `xml:"ows:Value" yaml:"value"` +} + +// ExtendedCapabilities struct for the WFS 2.0.0 +type ExtendedCapabilities struct { + ExtendedCapabilities struct { + Text string `xml:",chardata"` + MetadataURL struct { + Type string `xml:"xsi:type,attr" yaml:"type"` + URL string `xml:"inspire_common:URL" yaml:"url"` + MediaType string `xml:"inspire_common:MediaType" yaml:"mediatype"` + } `xml:"inspire_common:MetadataUrl" yaml:"metadataurl"` + SupportedLanguages struct { + DefaultLanguage struct { + Language string `xml:"inspire_common:Language" yaml:"language"` + } `xml:"inspire_common:DefaultLanguage" yaml:"defaultlanguage"` + } `xml:"inspire_common:SupportedLanguages" yaml:"supportedlanguages"` + ResponseLanguage struct { + Language string `xml:"inspire_common:Language" yaml:"language"` + } `xml:"inspire_common:ResponseLanguage" yaml:"responselanguage"` + SpatialDataSetIdentifier struct { + Code string `xml:"inspire_common:Code" yaml:"code"` + } `xml:"inspire_dls:SpatialDataSetIdentifier" yaml:"spatialdatasetidentifier"` + } `xml:"inspire_dls:ExtendedCapabilities" yaml:"extendedcapabilities"` +} + +// FeatureTypeList struct for the WFS 2.0.0 +type FeatureTypeList struct { + XMLName xml.Name `xml:"wfs:FeatureTypeList"` + FeatureType []FeatureType `xml:"wfs:FeatureType" yaml:"featuretype"` +} + +// FeatureType struct for the WFS 2.0.0 +type FeatureType struct { + Name string `xml:"wfs:Name" yaml:"name"` + Title string `xml:"wfs:Title" yaml:"title"` + Abstract string `xml:"wfs:Abstract" yaml:"abstract"` + Keywords []*struct { + Keyword []string `xml:"ows:Keyword" yaml:"keyword"` + } `xml:"ows:Keywords" yaml:"keywords"` + DefaultCRS *string `xml:"wfs:DefaultCRS" yaml:"defaultcrs"` + OtherCRS *[]string `xml:"wfs:OtherCRS" yaml:"othercrs"` + OutputFormats struct { + Format []string `xml:"wfs:Format" yaml:"format"` + } `xml:"wfs:OutputFormats" yaml:"outputformats"` + WGS84BoundingBox struct { + Dimensions string `xml:"dimensions,attr" yaml:"dimensions"` + LowerCorner string `xml:"ows:LowerCorner" yaml:"lowercorner"` + UpperCorner string `xml:"ows:UpperCorner" yaml:"uppercorner"` + } `xml:"ows:WGS84BoundingBox" yaml:"wgs84boundingbox"` + MetadataURL struct { + Href string `xml:"xlink:href,attr" yaml:"href"` + } `xml:"wfs:MetadataURL" yaml:"metadataurl"` +} + +// FilterCapabilities struct for the WFS 2.0.0 +type FilterCapabilities struct { + Conformance struct { + Constraint []struct { + Name string `xml:"name,attr" yaml:"name"` + NoValues string `xml:"ows:NoValues" yaml:"novalues"` + DefaultValue string `xml:"ows:DefaultValue" yaml:"defaultvalue"` + } `xml:"fes:Constraint" yaml:"constraint"` + } `xml:"fes:Conformance" yaml:"conformance"` + IDCapabilities struct { + ResourceIdentifier struct { + Name string `xml:"name,attr" yaml:"name" ` + } `xml:"fes:ResourceIdentifier" yaml:"resourceidentifier"` + } `xml:"fes:Id_Capabilities" yaml:"idcapabilities"` + ScalarCapabilities struct { + LogicalOperators string `xml:"fes:LogicalOperators" yaml:"logicaloperators"` + ComparisonOperators struct { + ComparisonOperator []struct { + Name string `xml:"name,attr"` + } `xml:"fes:ComparisonOperator" yaml:"comparisonoperator"` + } `xml:"fes:ComparisonOperators" yaml:"comparisonoperators"` + } `xml:"fes:Scalar_Capabilities" yaml:"scalarcapabilities"` + SpatialCapabilities struct { + GeometryOperands struct { + GeometryOperand []struct { + Name string `xml:"name,attr"` + } `xml:"fes:GeometryOperand"` + } `xml:"fes:GeometryOperands"` + SpatialOperators struct { + SpatialOperator []struct { + Name string `xml:"name,attr"` + } `xml:"fes:SpatialOperator"` + } `xml:"fes:SpatialOperators"` + } `xml:"fes:Spatial_Capabilities"` + // NO TemporalCapabilities!!! + TemporalCapabilities *TemporalCapabilities `xml:"fes:Temporal_Capabilities" yaml:"temporalcapabilities"` +} + +// TemporalCapabilities define but not used +type TemporalCapabilities struct { + TemporalOperands struct { + TemporalOperand []struct { + Name string `xml:"name,attr" yaml:"name"` + } `xml:"fes:TemporalOperand" yaml:"temporaloperand"` + } `xml:"fes:TemporalOperands" yaml:"temporaloperands"` + TemporalOperators struct { + TemporalOperator []struct { + Name string `xml:"name,attr,omitempty" yaml:"name,omitempty"` + } `xml:"fes:TemporalOperator" yaml:"temporaloperator"` + } `xml:"fes:TemporalOperators" yaml:"temporaloperators"` +} diff --git a/pkg/wfs200/response/getcapabilities.go b/pkg/wfs200/response/getcapabilities.go index 719f26c..bbed66c 100644 --- a/pkg/wfs200/response/getcapabilities.go +++ b/pkg/wfs200/response/getcapabilities.go @@ -5,6 +5,7 @@ import ( "regexp" "github.com/pdok/ogc-specifications/pkg/ows" + "github.com/pdok/ogc-specifications/pkg/wfs200/capabilities" ) // @@ -51,11 +52,11 @@ func (gc *GetCapabilities) BuildXML() []byte { type GetCapabilities struct { XMLName xml.Name `xml:"wfs:WFS_Capabilities"` Namespaces `yaml:"namespaces"` - ServiceIdentification ServiceIdentification `xml:"ows:ServiceIdentification" yaml:"serviceidentification"` - ServiceProvider ServiceProvider `xml:"ows:ServiceProvider" yaml:"serviceprovider"` - OperationsMetadata OperationsMetadata `xml:"ows:OperationsMetadata" yaml:"operationsmetadata"` - FeatureTypeList FeatureTypeList `xml:"wfs:FeatureTypeList" yaml:"featuretypelist"` - FilterCapabilities FilterCapabilities `xml:"fes:Filter_Capabilities" yaml:"filtercapabilities"` + ServiceIdentification ServiceIdentification `xml:"ows:ServiceIdentification" yaml:"serviceidentification"` + ServiceProvider ServiceProvider `xml:"ows:ServiceProvider" yaml:"serviceprovider"` + OperationsMetadata capabilities.OperationsMetadata `xml:"ows:OperationsMetadata" yaml:"operationsmetadata"` + FeatureTypeList capabilities.FeatureTypeList `xml:"wfs:FeatureTypeList" yaml:"featuretypelist"` + FilterCapabilities capabilities.FilterCapabilities `xml:"fes:Filter_Capabilities" yaml:"filtercapabilities"` } // Namespaces struct containing the namespaces needed for the XML document @@ -75,10 +76,12 @@ type Namespaces struct { // ServiceIdentification struct should only be fill by the "template" configuration wfs200.yaml type ServiceIdentification struct { - XMLName xml.Name `xml:"ows:ServiceIdentification"` - Title string `xml:"ows:Title" yaml:"title"` - Abstract string `xml:"ows:Abstract" yaml:"abstract"` - Keywords Keywords `xml:"ows:Keywords" yaml:"keywords"` + XMLName xml.Name `xml:"ows:ServiceIdentification"` + Title string `xml:"ows:Title" yaml:"title"` + Abstract string `xml:"ows:Abstract" yaml:"abstract"` + Keywords []struct { + Keyword []string `xml:"ows:Keyword" yaml:"keyword"` + } `xml:"ows:Keywords" yaml:"keywords"` ServiceType struct { Text string `xml:",chardata" yaml:"text"` CodeSpace string `xml:"codeSpace,attr" yaml:"codespace"` @@ -88,6 +91,11 @@ type ServiceIdentification struct { AccessConstraints string `xml:"ows:AccessConstraints" yaml:"accesscontraints"` } +// // Keywords in struct for repeatablity +// type Keywords struct { +// Keyword []string `xml:"ows:Keyword" yaml:"keyword"` +// } + // ServiceProvider struct containing the provider/organization information should only be fill by the "template" configuration wfs200.yaml type ServiceProvider struct { XMLName xml.Name `xml:"ows:ServiceProvider"` @@ -123,161 +131,3 @@ type ServiceProvider struct { Role string `xml:"ows:Role" yaml:"role"` } `xml:"ows:ServiceContact" yaml:"servicecontact"` } - -// Method in separated struct so to use it as a Pointer -type Method struct { - Type string `xml:"xlink:type,attr" yaml:"type"` - Href string `xml:"xlink:href,attr" yaml:"href"` -} - -// OperationsMetadata struct for the WFS 2.0.0 -type OperationsMetadata struct { - XMLName xml.Name `xml:"ows:OperationsMetadata"` - Operation []Operation `xml:"ows:Operation"` - Parameter struct { - Name string `xml:"name,attr" yaml:"name"` - AllowedValues struct { - Value []string `xml:"ows:Value" yaml:"value"` - } `xml:"ows:AllowedValues" yaml:"allowedvalues"` - } `xml:"ows:Parameter" yaml:"parameter"` - Constraint []Constraint `xml:"ows:Constraint" yaml:"constraint"` - ExtendedCapabilities *ExtendedCapabilities `xml:"ows:ExtendedCapabilities" yaml:"extendedcapabilities"` -} - -// Constraint struct for the WFS 2.0.0 -type Constraint struct { - Text string `xml:",chardata"` - Name string `xml:"name,attr" yaml:"name"` - NoValues *string `xml:"ows:NoValues" yaml:"novalue"` - DefaultValue *string `xml:"ows:DefaultValue" yaml:"defaultvalue"` - AllowedValues *AllowedValues `xml:"ows:AllowedValues" yaml:"allowedvalues"` -} - -// Operation struct for the WFS 2.0.0 -type Operation struct { - Name string `xml:"name,attr"` - DCP struct { - HTTP struct { - Get *Method `xml:"ows:Get,omitempty" yaml:"get,omitempty"` - Post *Method `xml:"ows:Post,omitempty" yaml:"post,omitempty"` - } `xml:"ows:HTTP" yaml:"http"` - } `xml:"ows:DCP" yaml:"dcp"` - Parameter []struct { - Name string `xml:"name,attr"` - AllowedValues struct { - Value []string `xml:"ows:Value"` - } `xml:"ows:AllowedValues"` - } `xml:"ows:Parameter"` -} - -// AllowedValues struct so it can be used as a pointer -type AllowedValues struct { - Value []string `xml:"ows:Value" yaml:"value"` -} - -// ExtendedCapabilities struct for the WFS 2.0.0 -type ExtendedCapabilities struct { - ExtendedCapabilities struct { - Text string `xml:",chardata"` - MetadataURL struct { - Type string `xml:"xsi:type,attr" yaml:"type"` - URL string `xml:"inspire_common:URL" yaml:"url"` - MediaType string `xml:"inspire_common:MediaType" yaml:"mediatype"` - } `xml:"inspire_common:MetadataUrl" yaml:"metadataurl"` - SupportedLanguages struct { - DefaultLanguage struct { - Language string `xml:"inspire_common:Language" yaml:"language"` - } `xml:"inspire_common:DefaultLanguage" yaml:"defaultlanguage"` - } `xml:"inspire_common:SupportedLanguages" yaml:"supportedlanguages"` - ResponseLanguage struct { - Language string `xml:"inspire_common:Language" yaml:"language"` - } `xml:"inspire_common:ResponseLanguage" yaml:"responselanguage"` - SpatialDataSetIdentifier struct { - Code string `xml:"inspire_common:Code" yaml:"code"` - } `xml:"inspire_dls:SpatialDataSetIdentifier" yaml:"spatialdatasetidentifier"` - } `xml:"inspire_dls:ExtendedCapabilities" yaml:"extendedcapabilities"` -} - -// FeatureTypeList struct for the WFS 2.0.0 -type FeatureTypeList struct { - XMLName xml.Name `xml:"wfs:FeatureTypeList"` - FeatureType []FeatureType `xml:"wfs:FeatureType" yaml:"featuretype"` -} - -// FeatureType struct for the WFS 2.0.0 -type FeatureType struct { - Name string `xml:"wfs:Name" yaml:"name"` - Title string `xml:"wfs:Title" yaml:"title"` - Abstract string `xml:"wfs:Abstract" yaml:"abstract"` - Keywords *Keywords `xml:"ows:Keywords" yaml:"keywords"` - DefaultCRS *string `xml:"wfs:DefaultCRS" yaml:"defaultcrs"` - OtherCRS *[]string `xml:"wfs:OtherCRS" yaml:"othercrs"` - OutputFormats struct { - Format []string `xml:"wfs:Format" yaml:"format"` - } `xml:"wfs:OutputFormats" yaml:"outputformats"` - WGS84BoundingBox struct { - Dimensions string `xml:"dimensions,attr" yaml:"dimensions"` - LowerCorner string `xml:"ows:LowerCorner" yaml:"lowercorner"` - UpperCorner string `xml:"ows:UpperCorner" yaml:"uppercorner"` - } `xml:"ows:WGS84BoundingBox" yaml:"wgs84boundingbox"` - MetadataURL struct { - Href string `xml:"xlink:href,attr" yaml:"href"` - } `xml:"wfs:MetadataURL" yaml:"metadataurl"` -} - -// FilterCapabilities struct for the WFS 2.0.0 -type FilterCapabilities struct { - Conformance struct { - Constraint []struct { - Name string `xml:"name,attr" yaml:"name"` - NoValues string `xml:"ows:NoValues" yaml:"novalues"` - DefaultValue string `xml:"ows:DefaultValue" yaml:"defaultvalue"` - } `xml:"fes:Constraint" yaml:"constraint"` - } `xml:"fes:Conformance" yaml:"conformance"` - IDCapabilities struct { - ResourceIdentifier struct { - Name string `xml:"name,attr" yaml:"name" ` - } `xml:"fes:ResourceIdentifier" yaml:"resourceidentifier"` - } `xml:"fes:Id_Capabilities" yaml:"idcapabilities"` - ScalarCapabilities struct { - LogicalOperators string `xml:"fes:LogicalOperators" yaml:"logicaloperators"` - ComparisonOperators struct { - ComparisonOperator []struct { - Name string `xml:"name,attr"` - } `xml:"fes:ComparisonOperator" yaml:"comparisonoperator"` - } `xml:"fes:ComparisonOperators" yaml:"comparisonoperators"` - } `xml:"fes:Scalar_Capabilities" yaml:"scalarcapabilities"` - SpatialCapabilities struct { - GeometryOperands struct { - GeometryOperand []struct { - Name string `xml:"name,attr"` - } `xml:"fes:GeometryOperand"` - } `xml:"fes:GeometryOperands"` - SpatialOperators struct { - SpatialOperator []struct { - Name string `xml:"name,attr"` - } `xml:"fes:SpatialOperator"` - } `xml:"fes:SpatialOperators"` - } `xml:"fes:Spatial_Capabilities"` - // NO TemporalCapabilities!!! - TemporalCapabilities *TemporalCapabilities `xml:"fes:Temporal_Capabilities" yaml:"temporalcapabilities"` -} - -// TemporalCapabilities define but not used -type TemporalCapabilities struct { - TemporalOperands struct { - TemporalOperand []struct { - Name string `xml:"name,attr" yaml:"name"` - } `xml:"fes:TemporalOperand" yaml:"temporaloperand"` - } `xml:"fes:TemporalOperands" yaml:"temporaloperands"` - TemporalOperators struct { - TemporalOperator []struct { - Name string `xml:"name,attr,omitempty" yaml:"name,omitempty"` - } `xml:"fes:TemporalOperator" yaml:"temporaloperator"` - } `xml:"fes:TemporalOperators" yaml:"temporaloperators"` -} - -// Keywords in struct for repeatablity -type Keywords struct { - Keyword []string `xml:"ows:Keyword" yaml:"keyword"` -} diff --git a/pkg/wms130/capabilities/capabilities.go b/pkg/wms130/capabilities/capabilities.go new file mode 100644 index 0000000..b56289a --- /dev/null +++ b/pkg/wms130/capabilities/capabilities.go @@ -0,0 +1,138 @@ +package capabilities + +// Capability base struct +type Capability struct { + Request Request `xml:"Request" yaml:"request"` + Exception Exception `xml:"Exception" yaml:"exception"` + ExtendedCapabilities *ExtendedCapabilities `xml:"inspire_vs:ExtendedCapabilities" yaml:"extendedcapabilities"` + Layer []Layer `xml:"Layer" yaml:"layer"` +} + +// Request struct with the different operations, should be filled from the template +type Request struct { + GetCapabilities RequestType `xml:"GetCapabilities" yaml:"getcapabilities"` + GetMap RequestType `xml:"GetMap" yaml:"getmap"` + GetFeatureInfo *RequestType `xml:"GetFeatureInfo" yaml:"getfeatureinfo"` +} + +// Exception struct containing the different available exceptions, should be filled from the template +type Exception struct { + Format []string `xml:"Format" yaml:"format"` +} + +// Layer contains the WMS 1.3.0 layer configuration +type Layer struct { + Queryable *string `xml:"queryable,attr" yaml:"queryable"` + // layer has a full/complete map coverage + Opaque *string `xml:"opaque,attr" yaml:"opaque"` + // no cascaded attr in Layer element, because we don't do cascaded services e.g. wms services "proxying" and/or combining other wms services + //Cascaded *string `xml:"cascaded,attr" yaml:"cascaded"` + Name *string `xml:"Name" yaml:"name"` + Title string `xml:"Title" yaml:"title"` + Abstract string `xml:"Abstract" yaml:"abstract"` + KeywordList *KeywordList `xml:"KeywordList" yaml:"keywordlist"` + CRS []*string `xml:"CRS" yaml:"crs"` + EXGeographicBoundingBox *EXGeographicBoundingBox `xml:"EX_GeographicBoundingBox" yaml:"exgeographicboundingbox"` + BoundingBox []*BoundingBox `xml:"BoundingBox" yaml:"boundingbox"` + AuthorityURL *AuthorityURL `xml:"AuthorityURL" yaml:"authorityurl"` + Identifier *Identifier `xml:"Identifier" yaml:"identifier"` + MetadataURL []*MetadataURL `xml:"MetadataURL" yaml:"metadataurl"` + Style []*Style `xml:"Style" yaml:"style"` + Layer []*Layer `xml:"Layer" yaml:"layer"` +} + +// RequestType containing the formats and DCPTypes available +type RequestType struct { + Format []string `xml:"Format" yaml:"format"` + DCPType DCPType `xml:"DCPType" yaml:"dcptype"` +} + +// Identifier in struct for repeatablity +type Identifier struct { + Authority string `xml:"authority,attr" yaml:"authority"` + Value string `xml:",chardata" yaml:"value"` +} + +// KeywordList in struct for repeatablity +type KeywordList struct { + Keyword []string `xml:"Keyword" yaml:"keyword"` +} + +// MetadataURL in struct for repeatablity +type MetadataURL struct { + Type *string `xml:"type,attr" yaml:"type"` + Format *string `xml:"Format" yaml:"format"` + OnlineResource OnlineResource `xml:"OnlineResource" yaml:"onlineresource"` +} + +// AuthorityURL in struct for repeatablity +type AuthorityURL struct { + Name string `xml:"name,attr" yaml:"name"` + OnlineResource OnlineResource `xml:"OnlineResource" yaml:"onlineresource"` +} + +// ExtendedCapabilities containing the inspire extendedcapbilities, when available +type ExtendedCapabilities struct { + MetadataURL struct { + Type string `xml:"xsi:type,attr" yaml:"type"` + URL string `xml:"inspire_common:URL" yaml:"url"` + MediaType string `xml:"inspire_common:MediaType" yaml:"mediatype"` + } `xml:"inspire_common:MetadataUrl" yaml:"metadataurl"` + SupportedLanguages struct { + DefaultLanguage struct { + Language string `xml:"inspire_common:Language" yaml:"language"` + } `xml:"inspire_common:DefaultLanguage" yaml:"defaultlanguage"` + } `xml:"inspire_common:SupportedLanguages" yaml:"supportedlanguages"` + ResponseLanguage struct { + Language string `xml:"inspire_common:Language" yaml:"language"` + } `xml:"inspire_common:ResponseLanguage" yaml:"responselanguage"` +} + +// EXGeographicBoundingBox in struct for repeatablity +type EXGeographicBoundingBox struct { + WestBoundLongitude string `xml:"westBoundLongitude" yaml:"westboundlongitude"` + EastBoundLongitude string `xml:"eastBoundLongitude" yaml:"eastboundlongitude"` + SouthBoundLatitude string `xml:"southBoundLatitude" yaml:"southboundlatitude"` + NorthBoundLatitude string `xml:"northBoundLatitude" yaml:"northboundlatitude"` +} + +// BoundingBox in struct for repeatablity +type BoundingBox struct { + CRS string `xml:"CRS,attr" yaml:"crs"` + Minx string `xml:"minx,attr" yaml:"minx"` + Miny string `xml:"miny,attr" yaml:"miny"` + Maxx string `xml:"maxx,attr" yaml:"maxx"` + Maxy string `xml:"maxy,attr" yaml:"maxy"` +} + +// Style in struct for repeatablity +type Style struct { + Name string `xml:"Name" yaml:"name"` + Title string `xml:"Title" yaml:"title"` + LegendURL struct { + Width string `xml:"width,attr" yaml:"width"` + Height string `xml:"height,attr" yaml:"height"` + Format string `xml:"Format" yaml:"format"` + OnlineResource OnlineResource `xml:"OnlineResource" yaml:"onlineresource"` + } `xml:"LegendURL" yaml:"legendurl"` +} + +// DCPType in struct for repeatablity +type DCPType struct { + HTTP struct { + Get *Method `xml:"Get" yaml:"get"` + Post *Method `xml:"Post" yaml:"post"` + } `xml:"HTTP" yaml:"http"` +} + +// Method in struct for repeatablity +type Method struct { + OnlineResource OnlineResource `xml:"OnlineResource" yaml:"onlineresource"` +} + +// OnlineResource in struct for repeatablity +type OnlineResource struct { + Xlink *string `xml:"xmlns:xlink,attr" yaml:"xlink"` + Type *string `xml:"xlink:type,attr" yaml:"type"` + Href *string `xml:"xlink:href,attr" yaml:"href"` +} diff --git a/pkg/wms130/response/getcapabilities.go b/pkg/wms130/response/getcapabilities.go index b72c432..6aaa85f 100644 --- a/pkg/wms130/response/getcapabilities.go +++ b/pkg/wms130/response/getcapabilities.go @@ -5,6 +5,7 @@ import ( "regexp" "github.com/pdok/ogc-specifications/pkg/ows" + "github.com/pdok/ogc-specifications/pkg/wms130/capabilities" ) // Contains the WMS130 struct @@ -51,8 +52,8 @@ func (gc *GetCapabilities) BuildXML() []byte { type GetCapabilities struct { XMLName xml.Name `xml:"WMS_Capabilities"` Namespaces `yaml:"namespaces"` - WMSService WMSService `xml:"Service" yaml:"service"` - Capability Capability `xml:"Capability" yaml:"capability"` + WMSService WMSService `xml:"Service" yaml:"service"` + Capability capabilities.Capability `xml:"Capability" yaml:"capability"` } // Namespaces struct containing the namespaces needed for the XML document @@ -75,7 +76,11 @@ type WMSService struct { KeywordList struct { Keyword []string `xml:"Keyword" yaml:"keyword"` } `xml:"KeywordList" yaml:"keywordlist"` - OnlineResource OnlineResource `xml:"OnlineResource" yaml:"onlineresource"` + OnlineResource struct { + Xlink *string `xml:"xmlns:xlink,attr" yaml:"xlink"` + Type *string `xml:"xlink:type,attr" yaml:"type"` + Href *string `xml:"xlink:href,attr" yaml:"href"` + } `xml:"OnlineResource" yaml:"onlineresource"` ContactInformation struct { ContactPersonPrimary struct { ContactPerson string `xml:"ContactPerson" yaml:"contactperson"` @@ -99,140 +104,3 @@ type WMSService struct { MaxWidth string `xml:"MaxWidth" yaml:"maxwidth"` MaxHeight string `xml:"MaxHeight" yaml:"maxheight"` } - -// Capability base struct -type Capability struct { - Request Request `xml:"Request" yaml:"request"` - Exception Exception `xml:"Exception" yaml:"exception"` - ExtendedCapabilities *ExtendedCapabilities `xml:"inspire_vs:ExtendedCapabilities" yaml:"extendedcapabilities"` - Layer []Layer `xml:"Layer" yaml:"layer"` -} - -// Request struct with the different operations, should be filled from the template -type Request struct { - GetCapabilities RequestType `xml:"GetCapabilities" yaml:"getcapabilities"` - GetMap RequestType `xml:"GetMap" yaml:"getmap"` - GetFeatureInfo *RequestType `xml:"GetFeatureInfo" yaml:"getfeatureinfo"` -} - -// Exception struct containing the different available exceptions, should be filled from the template -type Exception struct { - Format []string `xml:"Format" yaml:"format"` -} - -// Layer contains the WMS 1.3.0 layer configuration -type Layer struct { - Queryable *string `xml:"queryable,attr" yaml:"queryable"` - // layer has a full/complete map coverage - Opaque *string `xml:"opaque,attr" yaml:"opaque"` - // no cascaded attr in Layer element, because we don't do cascaded services e.g. wms services "proxying" and/or combining other wms services - //Cascaded *string `xml:"cascaded,attr" yaml:"cascaded"` - Name *string `xml:"Name" yaml:"name"` - Title string `xml:"Title" yaml:"title"` - Abstract string `xml:"Abstract" yaml:"abstract"` - KeywordList *KeywordList `xml:"KeywordList" yaml:"keywordlist"` - CRS []*string `xml:"CRS" yaml:"crs"` - EXGeographicBoundingBox *EXGeographicBoundingBox `xml:"EX_GeographicBoundingBox" yaml:"exgeographicboundingbox"` - BoundingBox []*BoundingBox `xml:"BoundingBox" yaml:"boundingbox"` - AuthorityURL *AuthorityURL `xml:"AuthorityURL" yaml:"authorityurl"` - Identifier *Identifier `xml:"Identifier" yaml:"identifier"` - MetadataURL []*MetadataURL `xml:"MetadataURL" yaml:"metadataurl"` - Style []*Style `xml:"Style" yaml:"style"` - Layer []*Layer `xml:"Layer" yaml:"layer"` -} - -// RequestType containing the formats and DCPTypes available -type RequestType struct { - Format []string `xml:"Format" yaml:"format"` - DCPType DCPType `xml:"DCPType" yaml:"dcptype"` -} - -// Identifier in struct for repeatablity -type Identifier struct { - Authority string `xml:"authority,attr" yaml:"authority"` - Value string `xml:",chardata" yaml:"value"` -} - -// KeywordList in struct for repeatablity -type KeywordList struct { - Keyword []string `xml:"Keyword" yaml:"keyword"` -} - -// OnlineResource in struct for repeatablity -type OnlineResource struct { - Xlink *string `xml:"xmlns:xlink,attr" yaml:"xlink"` - Type *string `xml:"xlink:type,attr" yaml:"type"` - Href *string `xml:"xlink:href,attr" yaml:"href"` -} - -// MetadataURL in struct for repeatablity -type MetadataURL struct { - Type *string `xml:"type,attr" yaml:"type"` - Format *string `xml:"Format" yaml:"format"` - OnlineResource OnlineResource `xml:"OnlineResource" yaml:"onlineresource"` -} - -// AuthorityURL in struct for repeatablity -type AuthorityURL struct { - Name string `xml:"name,attr" yaml:"name"` - OnlineResource OnlineResource `xml:"OnlineResource" yaml:"onlineresource"` -} - -// ExtendedCapabilities containing the inspire extendedcapbilities, when available -type ExtendedCapabilities struct { - MetadataURL struct { - Type string `xml:"xsi:type,attr" yaml:"type"` - URL string `xml:"inspire_common:URL" yaml:"url"` - MediaType string `xml:"inspire_common:MediaType" yaml:"mediatype"` - } `xml:"inspire_common:MetadataUrl" yaml:"metadataurl"` - SupportedLanguages struct { - DefaultLanguage struct { - Language string `xml:"inspire_common:Language" yaml:"language"` - } `xml:"inspire_common:DefaultLanguage" yaml:"defaultlanguage"` - } `xml:"inspire_common:SupportedLanguages" yaml:"supportedlanguages"` - ResponseLanguage struct { - Language string `xml:"inspire_common:Language" yaml:"language"` - } `xml:"inspire_common:ResponseLanguage" yaml:"responselanguage"` -} - -// EXGeographicBoundingBox in struct for repeatablity -type EXGeographicBoundingBox struct { - WestBoundLongitude string `xml:"westBoundLongitude" yaml:"westboundlongitude"` - EastBoundLongitude string `xml:"eastBoundLongitude" yaml:"eastboundlongitude"` - SouthBoundLatitude string `xml:"southBoundLatitude" yaml:"southboundlatitude"` - NorthBoundLatitude string `xml:"northBoundLatitude" yaml:"northboundlatitude"` -} - -// BoundingBox in struct for repeatablity -type BoundingBox struct { - CRS string `xml:"CRS,attr" yaml:"crs"` - Minx string `xml:"minx,attr" yaml:"minx"` - Miny string `xml:"miny,attr" yaml:"miny"` - Maxx string `xml:"maxx,attr" yaml:"maxx"` - Maxy string `xml:"maxy,attr" yaml:"maxy"` -} - -// Style in struct for repeatablity -type Style struct { - Name string `xml:"Name" yaml:"name"` - Title string `xml:"Title" yaml:"title"` - LegendURL struct { - Width string `xml:"width,attr" yaml:"width"` - Height string `xml:"height,attr" yaml:"height"` - Format string `xml:"Format" yaml:"format"` - OnlineResource OnlineResource `xml:"OnlineResource" yaml:"onlineresource"` - } `xml:"LegendURL" yaml:"legendurl"` -} - -// DCPType in struct for repeatablity -type DCPType struct { - HTTP struct { - Get *Method `xml:"Get" yaml:"get"` - Post *Method `xml:"Post" yaml:"post"` - } `xml:"HTTP" yaml:"http"` -} - -// Method in struct for repeatablity -type Method struct { - OnlineResource OnlineResource `xml:"OnlineResource" yaml:"onlineresource"` -} diff --git a/pkg/wms130/validation/validator_test.go b/pkg/wms130/validation/validator_test.go index 16152a3..3be4ea5 100644 --- a/pkg/wms130/validation/validator_test.go +++ b/pkg/wms130/validation/validator_test.go @@ -9,6 +9,7 @@ import ( "github.com/go-playground/validator/v10" en_translations "github.com/go-playground/validator/v10/translations/en" "github.com/pdok/ogc-specifications/pkg/ows" + "github.com/pdok/ogc-specifications/pkg/wms130/capabilities" "github.com/pdok/ogc-specifications/pkg/wms130/request" "github.com/pdok/ogc-specifications/pkg/wms130/response" ) @@ -31,8 +32,8 @@ func TestValidation(t *testing.T) { getcapabilities := response.GetCapabilities{ WMSService: response.WMSService{Name: "RiversRoadsAndHouses"}, - Capability: response.Capability{ - Layer: []response.Layer{ + Capability: capabilities.Capability{ + Layer: []capabilities.Layer{ {Name: sp(`Rivers`), CRS: []*string{sp(`EPSG:4326`)}}, {Name: sp(`Roads`), CRS: []*string{sp(`EPSG:4326`)}}, {Name: sp(`Houses`), CRS: []*string{sp(`EPSG:4326`)}}, diff --git a/pkg/wmts100/capabilities/capabilities.go b/pkg/wmts100/capabilities/capabilities.go new file mode 100644 index 0000000..617900f --- /dev/null +++ b/pkg/wmts100/capabilities/capabilities.go @@ -0,0 +1,62 @@ +package capabilities + +// Contents struct for the WMTS 1.0.0 +type Contents struct { + Layer []Layer `xml:"Layer" yaml:"layer"` + TileMatrixSet []TileMatrixSet `xml:"TileMatrixSet" yaml:"tilematrixset"` +} + +// GetTilematrixsets helper function for collecting the provided TileMatrixSets, so th base can be cleanup for unused TileMatrixSets +func (c Contents) GetTilematrixsets() map[string]bool { + tilematrixsets := make(map[string]bool) + for _, l := range c.Layer { + for _, t := range l.TileMatrixSetLink { + tilematrixsets[t.TileMatrixSet] = true + } + } + return tilematrixsets +} + +// Layer in struct for repeatablity +type Layer struct { + Title string `xml:"ows:Title" yaml:"title"` + Abstract string `xml:"ows:Abstract" yaml:"abstract"` + WGS84BoundingBox struct { + LowerCorner string `xml:"ows:LowerCorner" yaml:"lowercorner"` + UpperCorner string `xml:"ows:UpperCorner" yaml:"uppercorner"` + } `xml:"ows:WGS84BoundingBox" yaml:"wgs84boundingbox"` + Identifier string `xml:"ows:Identifier" yaml:"identifier"` + Style struct { + Identifier string `xml:"ows:Identifier" yaml:"identifier"` + } `xml:"Style" yaml:"style"` + Format string `xml:"Format" yaml:"format"` + TileMatrixSetLink []TileMatrixSetLink `xml:"TileMatrixSetLink" yaml:"tilematrixsetlink"` + ResourceURL struct { + Format string `xml:"format,attr" yaml:"format"` + ResourceType string `xml:"resourceType,attr" yaml:"resourcetype"` + Template string `xml:"template,attr" yaml:"template"` + } `xml:"ResourceURL" yaml:"resourceurl"` +} + +// TileMatrixSetLink in struct for repeatablity +type TileMatrixSetLink struct { + TileMatrixSet string `xml:"TileMatrixSet" yaml:"tilematrixset"` +} + +// TileMatrixSet in struct for repeatablity +type TileMatrixSet struct { + Identifier string `xml:"ows:Identifier" yaml:"identifier"` + SupportedCRS string `xml:"ows:SupportedCRS" yaml:"supportedcrs"` + TileMatrix []TileMatrix `xml:"TileMatrix" yaml:"tilematrix"` +} + +// TileMatrix in struct for repeatablity +type TileMatrix struct { + Identifier string `xml:"ows:Identifier" yaml:"identifier"` + ScaleDenominator string `xml:"ScaleDenominator" yaml:"scaledenominator"` + TopLeftCorner string `xml:"TopLeftCorner" yaml:"topleftcorner"` + TileWidth string `xml:"TileWidth" yaml:"tilewidth"` + TileHeight string `xml:"TileHeight" yaml:"tileheight"` + MatrixWidth string `xml:"MatrixWidth" yaml:"matrixwidth"` + MatrixHeight string `xml:"MatrixHeight" yaml:"matrixheight"` +} diff --git a/pkg/wmts100/response/getcapabilities.go b/pkg/wmts100/response/getcapabilities.go index 4cf29ef..f8020e7 100644 --- a/pkg/wmts100/response/getcapabilities.go +++ b/pkg/wmts100/response/getcapabilities.go @@ -5,6 +5,7 @@ import ( "regexp" "github.com/pdok/ogc-specifications/pkg/ows" + "github.com/pdok/ogc-specifications/pkg/wmts100/capabilities" ) // @@ -50,7 +51,7 @@ type GetCapabilities struct { XMLName xml.Name `xml:"Capabilities"` Namespaces `yaml:"namespaces"` ServiceIdentification ServiceIdentification `xml:"ows:ServiceIdentification" yaml:"serviceidentification"` - Contents Contents `xml:"Contents" yaml:"contents"` + Contents capabilities.Contents `xml:"Contents" yaml:"contents"` ServiceMetadataURL ServiceMetadataURL `xml:"ServiceMetadataURL" yaml:"servicemetadataurl"` } @@ -75,67 +76,6 @@ type ServiceIdentification struct { AccessConstraints string `xml:"ows:AccessConstraints" yaml:"accessconstraints"` } -// Contents struct for the WMTS 1.0.0 -type Contents struct { - Layer []Layer `xml:"Layer" yaml:"layer"` - TileMatrixSet []TileMatrixSet `xml:"TileMatrixSet" yaml:"tilematrixset"` -} - -// GetTilematrixsets helper function for collecting the provided TileMatrixSets, so th base can be cleanup for unused TileMatrixSets -func (c Contents) GetTilematrixsets() map[string]bool { - tilematrixsets := make(map[string]bool) - for _, l := range c.Layer { - for _, t := range l.TileMatrixSetLink { - tilematrixsets[t.TileMatrixSet] = true - } - } - return tilematrixsets -} - -// Layer in struct for repeatablity -type Layer struct { - Title string `xml:"ows:Title" yaml:"title"` - Abstract string `xml:"ows:Abstract" yaml:"abstract"` - WGS84BoundingBox struct { - LowerCorner string `xml:"ows:LowerCorner" yaml:"lowercorner"` - UpperCorner string `xml:"ows:UpperCorner" yaml:"uppercorner"` - } `xml:"ows:WGS84BoundingBox" yaml:"wgs84boundingbox"` - Identifier string `xml:"ows:Identifier" yaml:"identifier"` - Style struct { - Identifier string `xml:"ows:Identifier" yaml:"identifier"` - } `xml:"Style" yaml:"style"` - Format string `xml:"Format" yaml:"format"` - TileMatrixSetLink []TileMatrixSetLink `xml:"TileMatrixSetLink" yaml:"tilematrixsetlink"` - ResourceURL struct { - Format string `xml:"format,attr" yaml:"format"` - ResourceType string `xml:"resourceType,attr" yaml:"resourcetype"` - Template string `xml:"template,attr" yaml:"template"` - } `xml:"ResourceURL" yaml:"resourceurl"` -} - -// TileMatrixSetLink in struct for repeatablity -type TileMatrixSetLink struct { - TileMatrixSet string `xml:"TileMatrixSet" yaml:"tilematrixset"` -} - -// TileMatrixSet in struct for repeatablity -type TileMatrixSet struct { - Identifier string `xml:"ows:Identifier" yaml:"identifier"` - SupportedCRS string `xml:"ows:SupportedCRS" yaml:"supportedcrs"` - TileMatrix []TileMatrix `xml:"TileMatrix" yaml:"tilematrix"` -} - -// TileMatrix in struct for repeatablity -type TileMatrix struct { - Identifier string `xml:"ows:Identifier" yaml:"identifier"` - ScaleDenominator string `xml:"ScaleDenominator" yaml:"scaledenominator"` - TopLeftCorner string `xml:"TopLeftCorner" yaml:"topleftcorner"` - TileWidth string `xml:"TileWidth" yaml:"tilewidth"` - TileHeight string `xml:"TileHeight" yaml:"tileheight"` - MatrixWidth string `xml:"MatrixWidth" yaml:"matrixwidth"` - MatrixHeight string `xml:"MatrixHeight" yaml:"matrixheight"` -} - // ServiceMetadataURL in struct for repeatablity type ServiceMetadataURL struct { Href string `xml:"xlink:href,attr" yaml:"href"` From 4e2896678e05b8612e8904c7ce51138c3e72be1e Mon Sep 17 00:00:00 2001 From: Wouter Date: Mon, 27 Jul 2020 20:02:42 +0200 Subject: [PATCH 04/45] small fix README.md --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index d72123b..7f35820 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ -![GitHub license](https://img.shields.io/github/license/PDOK/ogc-specifications) -[![GitHub release](https://img.shields.io/github/release/PDOK/ogc-specifications.svg)](https://github.com/PDOK/ogc-specifications/releases) -[![Go Report Card](https://goreportcard.com/badge/PDOK/ogc-specifications)](https://goreportcard.com/report/PDOK/ogc-specifications) - # ogc-specifications +![GitHub license](https://img.shields.io/github/license/PDOK/ogc-specifications) +[![GitHub release](https://img.shields.io/github/release/PDOK/ogc-specifications.svg)](https://github.com/PDOK/ogc-specifications/releases) +[![Go Report Card](https://goreportcard.com/badge/PDOK/ogc-specifications)](https://goreportcard.com/report/PDOK/ogc-specifications) + The package ogc-specifications is a implementation of the OGC Webservice Specifications as defined by the [OGC](https://www.ogc.org/). This package has support for the following OGC Webservice Specifications: From 36a48d8d0969386d2ded533191f6c11a19fc2567 Mon Sep 17 00:00:00 2001 From: Wouter Date: Mon, 27 Jul 2020 20:04:14 +0200 Subject: [PATCH 05/45] go mod tidy --- go.mod | 5 +---- go.sum | 19 ++++--------------- 2 files changed, 5 insertions(+), 19 deletions(-) diff --git a/go.mod b/go.mod index 64e8f21..705c9a9 100644 --- a/go.mod +++ b/go.mod @@ -1,13 +1,10 @@ module github.com/pdok/ogc-specifications -go 1.13 +go 1.14 require ( github.com/go-playground/locales v0.13.0 github.com/go-playground/universal-translator v0.17.0 github.com/go-playground/validator/v10 v10.3.0 - github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99 // indirect - github.com/ianlancetaylor/demangle v0.0.0-20200715173712-053cf528c12f // indirect - golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae // indirect gopkg.in/yaml.v2 v2.3.0 ) diff --git a/go.sum b/go.sum index 37d37c4..cac2ae7 100644 --- a/go.sum +++ b/go.sum @@ -1,31 +1,20 @@ -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= -github.com/go-playground/validator v9.31.0+incompatible h1:UA72EPEogEnq76ehGdEDp4Mit+3FDh548oRqwVgNsHA= github.com/go-playground/validator/v10 v10.3.0 h1:nZU+7q+yJoFmwvNgv/LnPUkwPal62+b2xXj0AU1Es7o= github.com/go-playground/validator/v10 v10.3.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99 h1:Ak8CrdlwwXwAZxzS66vgPt4U8yUZX7JwLvVR58FN5jM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6 h1:UDMh68UUwekSh5iP2OMhRRZJiiBccgV7axzUG8vi56c= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/ianlancetaylor/demangle v0.0.0-20200715173712-053cf528c12f h1:nTaA/z8mev5oJv1dNSbu8Pwvu5CJyBZKwDvHpr9FZ4I= -github.com/ianlancetaylor/demangle v0.0.0-20200715173712-053cf528c12f/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e h1:9vRrk9YW2BTzLP0VCB9ZDjU4cPqkg+IDWL7XgxA1yxQ= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae h1:Ih9Yo4hSPImZOpfGuA4bR/ORKTAbhZo2AbWNRCnevdo= -golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= From 729fe9c50a454860b63d78cf3297ac7a442ecee6 Mon Sep 17 00:00:00 2001 From: Wouter Date: Mon, 27 Jul 2020 20:15:11 +0200 Subject: [PATCH 06/45] moved Keyword -> ows pkg --- pkg/ows/common.go | 5 +++++ pkg/wcs201/response/getcapabilities.go | 8 +++----- pkg/wfs200/capabilities/capabilities.go | 20 +++++++++++--------- pkg/wfs200/response/getcapabilities.go | 10 ++++------ pkg/wms130/capabilities/capabilities.go | 12 +++++++----- pkg/wms130/response/getcapabilities.go | 10 ++++------ 6 files changed, 34 insertions(+), 31 deletions(-) diff --git a/pkg/ows/common.go b/pkg/ows/common.go index 4073803..3cb58cb 100644 --- a/pkg/ows/common.go +++ b/pkg/ows/common.go @@ -28,6 +28,11 @@ func (b *BoundingBox) BuildKVP() string { return fmt.Sprintf("%f,%f,%f,%f", b.LowerCorner[0], b.LowerCorner[1], b.UpperCorner[0], b.UpperCorner[1]) } +// KeywordList in struct for repeatablity +type Keywords struct { + Keyword []string `xml:"Keyword" yaml:"keyword"` +} + // StripDuplicateAttr removes the duplicate Attributes from a []Attribute func StripDuplicateAttr(attr []xml.Attr) []xml.Attr { attributemap := make(map[xml.Name]string) diff --git a/pkg/wcs201/response/getcapabilities.go b/pkg/wcs201/response/getcapabilities.go index dcef702..434aafe 100644 --- a/pkg/wcs201/response/getcapabilities.go +++ b/pkg/wcs201/response/getcapabilities.go @@ -79,11 +79,9 @@ type Namespaces struct { // ServiceIdentification struct should only be fill by the "template" configuration wcs201.yaml type ServiceIdentification struct { - Title string `xml:"ows:Title" yaml:"title"` - Abstract string `xml:"ows:Abstract" yaml:"abstract"` - Keywords struct { - Keyword []string `xml:"ows:Keyword" yaml:"keyword"` - } `xml:"ows:Keywords" yaml:"keywords"` + Title string `xml:"ows:Title" yaml:"title"` + Abstract string `xml:"ows:Abstract" yaml:"abstract"` + Keywords *ows.Keywords `xml:"ows:Keywords" yaml:"keywords"` ServiceType struct { Text string `xml:",chardata" yaml:"text"` CodeSpace string `xml:"codeSpace,attr" yaml:"codespace"` diff --git a/pkg/wfs200/capabilities/capabilities.go b/pkg/wfs200/capabilities/capabilities.go index a7967ef..95846e9 100644 --- a/pkg/wfs200/capabilities/capabilities.go +++ b/pkg/wfs200/capabilities/capabilities.go @@ -1,6 +1,10 @@ package capabilities -import "encoding/xml" +import ( + "encoding/xml" + + "github.com/pdok/ogc-specifications/pkg/ows" +) // Method in separated struct so to use it as a Pointer type Method struct { @@ -84,14 +88,12 @@ type FeatureTypeList struct { // FeatureType struct for the WFS 2.0.0 type FeatureType struct { - Name string `xml:"wfs:Name" yaml:"name"` - Title string `xml:"wfs:Title" yaml:"title"` - Abstract string `xml:"wfs:Abstract" yaml:"abstract"` - Keywords []*struct { - Keyword []string `xml:"ows:Keyword" yaml:"keyword"` - } `xml:"ows:Keywords" yaml:"keywords"` - DefaultCRS *string `xml:"wfs:DefaultCRS" yaml:"defaultcrs"` - OtherCRS *[]string `xml:"wfs:OtherCRS" yaml:"othercrs"` + Name string `xml:"wfs:Name" yaml:"name"` + Title string `xml:"wfs:Title" yaml:"title"` + Abstract string `xml:"wfs:Abstract" yaml:"abstract"` + Keywords *ows.Keywords `xml:"ows:Keywords" yaml:"keywords"` + DefaultCRS *string `xml:"wfs:DefaultCRS" yaml:"defaultcrs"` + OtherCRS *[]string `xml:"wfs:OtherCRS" yaml:"othercrs"` OutputFormats struct { Format []string `xml:"wfs:Format" yaml:"format"` } `xml:"wfs:OutputFormats" yaml:"outputformats"` diff --git a/pkg/wfs200/response/getcapabilities.go b/pkg/wfs200/response/getcapabilities.go index bbed66c..4c0c401 100644 --- a/pkg/wfs200/response/getcapabilities.go +++ b/pkg/wfs200/response/getcapabilities.go @@ -76,12 +76,10 @@ type Namespaces struct { // ServiceIdentification struct should only be fill by the "template" configuration wfs200.yaml type ServiceIdentification struct { - XMLName xml.Name `xml:"ows:ServiceIdentification"` - Title string `xml:"ows:Title" yaml:"title"` - Abstract string `xml:"ows:Abstract" yaml:"abstract"` - Keywords []struct { - Keyword []string `xml:"ows:Keyword" yaml:"keyword"` - } `xml:"ows:Keywords" yaml:"keywords"` + XMLName xml.Name `xml:"ows:ServiceIdentification"` + Title string `xml:"ows:Title" yaml:"title"` + Abstract string `xml:"ows:Abstract" yaml:"abstract"` + Keywords *ows.Keywords `xml:"ows:Keywords" yaml:"keywords"` ServiceType struct { Text string `xml:",chardata" yaml:"text"` CodeSpace string `xml:"codeSpace,attr" yaml:"codespace"` diff --git a/pkg/wms130/capabilities/capabilities.go b/pkg/wms130/capabilities/capabilities.go index b56289a..024de75 100644 --- a/pkg/wms130/capabilities/capabilities.go +++ b/pkg/wms130/capabilities/capabilities.go @@ -1,5 +1,7 @@ package capabilities +import "github.com/pdok/ogc-specifications/pkg/ows" + // Capability base struct type Capability struct { Request Request `xml:"Request" yaml:"request"` @@ -30,7 +32,7 @@ type Layer struct { Name *string `xml:"Name" yaml:"name"` Title string `xml:"Title" yaml:"title"` Abstract string `xml:"Abstract" yaml:"abstract"` - KeywordList *KeywordList `xml:"KeywordList" yaml:"keywordlist"` + KeywordList *ows.Keywords `xml:"KeywordList" yaml:"keywordlist"` CRS []*string `xml:"CRS" yaml:"crs"` EXGeographicBoundingBox *EXGeographicBoundingBox `xml:"EX_GeographicBoundingBox" yaml:"exgeographicboundingbox"` BoundingBox []*BoundingBox `xml:"BoundingBox" yaml:"boundingbox"` @@ -53,10 +55,10 @@ type Identifier struct { Value string `xml:",chardata" yaml:"value"` } -// KeywordList in struct for repeatablity -type KeywordList struct { - Keyword []string `xml:"Keyword" yaml:"keyword"` -} +// // KeywordList in struct for repeatablity +// type KeywordList struct { +// Keyword []string `xml:"Keyword" yaml:"keyword"` +// } // MetadataURL in struct for repeatablity type MetadataURL struct { diff --git a/pkg/wms130/response/getcapabilities.go b/pkg/wms130/response/getcapabilities.go index 6aaa85f..48f959f 100644 --- a/pkg/wms130/response/getcapabilities.go +++ b/pkg/wms130/response/getcapabilities.go @@ -70,12 +70,10 @@ type Namespaces struct { // WMSService struct containing the base service information filled from the template type WMSService struct { - Name string `xml:"Name" yaml:"name"` - Title string `xml:"Title" yaml:"title"` - Abstract string `xml:"Abstract" yaml:"abstract"` - KeywordList struct { - Keyword []string `xml:"Keyword" yaml:"keyword"` - } `xml:"KeywordList" yaml:"keywordlist"` + Name string `xml:"Name" yaml:"name"` + Title string `xml:"Title" yaml:"title"` + Abstract string `xml:"Abstract" yaml:"abstract"` + KeywordList *ows.Keywords `xml:"KeywordList" yaml:"keywordlist"` OnlineResource struct { Xlink *string `xml:"xmlns:xlink,attr" yaml:"xlink"` Type *string `xml:"xlink:type,attr" yaml:"type"` From 4de9344218eb70036e55a845281914020c378a1b Mon Sep 17 00:00:00 2001 From: Wouter Date: Mon, 27 Jul 2020 20:24:24 +0200 Subject: [PATCH 07/45] fixes so types & usage of ows.BoundingBox --- pkg/wfs200/capabilities/capabilities.go | 10 +++------ pkg/wfs200/response/getcapabilities.go | 5 ----- pkg/wms130/capabilities/capabilities.go | 27 ++++++++++--------------- pkg/wms130/response/getcapabilities.go | 4 ++-- 4 files changed, 16 insertions(+), 30 deletions(-) diff --git a/pkg/wfs200/capabilities/capabilities.go b/pkg/wfs200/capabilities/capabilities.go index 95846e9..d8ef634 100644 --- a/pkg/wfs200/capabilities/capabilities.go +++ b/pkg/wfs200/capabilities/capabilities.go @@ -97,12 +97,8 @@ type FeatureType struct { OutputFormats struct { Format []string `xml:"wfs:Format" yaml:"format"` } `xml:"wfs:OutputFormats" yaml:"outputformats"` - WGS84BoundingBox struct { - Dimensions string `xml:"dimensions,attr" yaml:"dimensions"` - LowerCorner string `xml:"ows:LowerCorner" yaml:"lowercorner"` - UpperCorner string `xml:"ows:UpperCorner" yaml:"uppercorner"` - } `xml:"ows:WGS84BoundingBox" yaml:"wgs84boundingbox"` - MetadataURL struct { + WGS84BoundingBox ows.BoundingBox `xml:"ows:WGS84BoundingBox" yaml:"wgs84boundingbox"` + MetadataURL struct { Href string `xml:"xlink:href,attr" yaml:"href"` } `xml:"wfs:MetadataURL" yaml:"metadataurl"` } @@ -118,7 +114,7 @@ type FilterCapabilities struct { } `xml:"fes:Conformance" yaml:"conformance"` IDCapabilities struct { ResourceIdentifier struct { - Name string `xml:"name,attr" yaml:"name" ` + Name string `xml:"name,attr" yaml:"name"` } `xml:"fes:ResourceIdentifier" yaml:"resourceidentifier"` } `xml:"fes:Id_Capabilities" yaml:"idcapabilities"` ScalarCapabilities struct { diff --git a/pkg/wfs200/response/getcapabilities.go b/pkg/wfs200/response/getcapabilities.go index 4c0c401..c459d13 100644 --- a/pkg/wfs200/response/getcapabilities.go +++ b/pkg/wfs200/response/getcapabilities.go @@ -89,11 +89,6 @@ type ServiceIdentification struct { AccessConstraints string `xml:"ows:AccessConstraints" yaml:"accesscontraints"` } -// // Keywords in struct for repeatablity -// type Keywords struct { -// Keyword []string `xml:"ows:Keyword" yaml:"keyword"` -// } - // ServiceProvider struct containing the provider/organization information should only be fill by the "template" configuration wfs200.yaml type ServiceProvider struct { XMLName xml.Name `xml:"ows:ServiceProvider"` diff --git a/pkg/wms130/capabilities/capabilities.go b/pkg/wms130/capabilities/capabilities.go index 024de75..d02f900 100644 --- a/pkg/wms130/capabilities/capabilities.go +++ b/pkg/wms130/capabilities/capabilities.go @@ -55,11 +55,6 @@ type Identifier struct { Value string `xml:",chardata" yaml:"value"` } -// // KeywordList in struct for repeatablity -// type KeywordList struct { -// Keyword []string `xml:"Keyword" yaml:"keyword"` -// } - // MetadataURL in struct for repeatablity type MetadataURL struct { Type *string `xml:"type,attr" yaml:"type"` @@ -92,19 +87,19 @@ type ExtendedCapabilities struct { // EXGeographicBoundingBox in struct for repeatablity type EXGeographicBoundingBox struct { - WestBoundLongitude string `xml:"westBoundLongitude" yaml:"westboundlongitude"` - EastBoundLongitude string `xml:"eastBoundLongitude" yaml:"eastboundlongitude"` - SouthBoundLatitude string `xml:"southBoundLatitude" yaml:"southboundlatitude"` - NorthBoundLatitude string `xml:"northBoundLatitude" yaml:"northboundlatitude"` + WestBoundLongitude float64 `xml:"westBoundLongitude" yaml:"westboundlongitude"` + EastBoundLongitude float64 `xml:"eastBoundLongitude" yaml:"eastboundlongitude"` + SouthBoundLatitude float64 `xml:"southBoundLatitude" yaml:"southboundlatitude"` + NorthBoundLatitude float64 `xml:"northBoundLatitude" yaml:"northboundlatitude"` } // BoundingBox in struct for repeatablity type BoundingBox struct { - CRS string `xml:"CRS,attr" yaml:"crs"` - Minx string `xml:"minx,attr" yaml:"minx"` - Miny string `xml:"miny,attr" yaml:"miny"` - Maxx string `xml:"maxx,attr" yaml:"maxx"` - Maxy string `xml:"maxy,attr" yaml:"maxy"` + CRS string `xml:"CRS,attr" yaml:"crs"` + Minx float64 `xml:"minx,attr" yaml:"minx"` + Miny float64 `xml:"miny,attr" yaml:"miny"` + Maxx float64 `xml:"maxx,attr" yaml:"maxx"` + Maxy float64 `xml:"maxy,attr" yaml:"maxy"` } // Style in struct for repeatablity @@ -112,8 +107,8 @@ type Style struct { Name string `xml:"Name" yaml:"name"` Title string `xml:"Title" yaml:"title"` LegendURL struct { - Width string `xml:"width,attr" yaml:"width"` - Height string `xml:"height,attr" yaml:"height"` + Width int `xml:"width,attr" yaml:"width"` + Height int `xml:"height,attr" yaml:"height"` Format string `xml:"Format" yaml:"format"` OnlineResource OnlineResource `xml:"OnlineResource" yaml:"onlineresource"` } `xml:"LegendURL" yaml:"legendurl"` diff --git a/pkg/wms130/response/getcapabilities.go b/pkg/wms130/response/getcapabilities.go index 48f959f..76c88e9 100644 --- a/pkg/wms130/response/getcapabilities.go +++ b/pkg/wms130/response/getcapabilities.go @@ -99,6 +99,6 @@ type WMSService struct { } `xml:"ContactInformation"` Fees string `xml:"Fees" yaml:"fees"` AccessConstraints string `xml:"AccessConstraints" yaml:"accessconstraints"` - MaxWidth string `xml:"MaxWidth" yaml:"maxwidth"` - MaxHeight string `xml:"MaxHeight" yaml:"maxheight"` + MaxWidth int `xml:"MaxWidth" yaml:"maxwidth"` + MaxHeight int `xml:"MaxHeight" yaml:"maxheight"` } From 3b437eb95b25061b8a0f13b19ff61e3fd8869cdb Mon Sep 17 00:00:00 2001 From: Wouter Date: Mon, 27 Jul 2020 20:38:25 +0200 Subject: [PATCH 08/45] move BoundingBox -> common in ows pkg --- pkg/ows/common.go | 39 +++++++++++++++- pkg/ows/common_test.go | 28 ++++++++++++ pkg/wms130/request/getfeatureinfo.go | 3 +- pkg/wms130/request/getmap.go | 68 ++++++++++++++-------------- pkg/wms130/request/getmap_test.go | 29 +----------- 5 files changed, 102 insertions(+), 65 deletions(-) diff --git a/pkg/ows/common.go b/pkg/ows/common.go index 3cb58cb..4d3e10e 100644 --- a/pkg/ows/common.go +++ b/pkg/ows/common.go @@ -3,6 +3,8 @@ package ows import ( "encoding/xml" "fmt" + "strconv" + "strings" ) // BoundingBox struct @@ -28,7 +30,42 @@ func (b *BoundingBox) BuildKVP() string { return fmt.Sprintf("%f,%f,%f,%f", b.LowerCorner[0], b.LowerCorner[1], b.UpperCorner[0], b.UpperCorner[1]) } -// KeywordList in struct for repeatablity +//Build builds a BoundingBox +func (b *BoundingBox) Build(boundingbox string) Exception { + result := strings.Split(boundingbox, ",") + var lx, ly, ux, uy float64 + var err error + + if len(result) < 4 { + return InvalidParameterValue(boundingbox, `boundingbox`) + } + + if len(result) == 4 || len(result) == 5 { + if lx, err = strconv.ParseFloat(result[0], 64); err != nil { + return InvalidParameterValue(boundingbox, `boundingbox`) + } + if ly, err = strconv.ParseFloat(result[1], 64); err != nil { + return InvalidParameterValue(boundingbox, `boundingbox`) + } + if ux, err = strconv.ParseFloat(result[2], 64); err != nil { + return InvalidParameterValue(boundingbox, `boundingbox`) + } + if uy, err = strconv.ParseFloat(result[3], 64); err != nil { + return InvalidParameterValue(boundingbox, `boundingbox`) + } + } + + b.LowerCorner = [2]float64{lx, ly} + b.UpperCorner = [2]float64{ux, uy} + + if len(result) == 5 { + b.Crs = result[4] + } + + return nil +} + +// Keywords in struct for repeatablity type Keywords struct { Keyword []string `xml:"Keyword" yaml:"keyword"` } diff --git a/pkg/ows/common_test.go b/pkg/ows/common_test.go index a50ee05..e21bd85 100644 --- a/pkg/ows/common_test.go +++ b/pkg/ows/common_test.go @@ -22,6 +22,34 @@ func TestBoundingBoxBuildQueryString(t *testing.T) { } } +func TestBuildBoundingBox(t *testing.T) { + var tests = []struct { + boundingbox string + bbox BoundingBox + Exception Exception + }{ + 0: {boundingbox: "0,0,100,100", bbox: BoundingBox{LowerCorner: [2]float64{0, 0}, UpperCorner: [2]float64{100, 100}}}, + 1: {boundingbox: "0,0,-100,-100", bbox: BoundingBox{LowerCorner: [2]float64{0, 0}, UpperCorner: [2]float64{-100, -100}}}, // while this isn't correct, this will be 'addressed' in the validation step + 2: {boundingbox: "0,0,100", Exception: InvalidParameterValue(`0,0,100`, `boundingbox`)}, + 3: {boundingbox: ",,,", Exception: InvalidParameterValue(`,,,`, `boundingbox`)}, + 4: {boundingbox: ",,,100", Exception: InvalidParameterValue(`,,,100`, `boundingbox`)}, + 5: {boundingbox: "number,,,100", Exception: InvalidParameterValue(`number,,,100`, `boundingbox`)}, + } + + for k, test := range tests { + var bbox BoundingBox + if err := bbox.Build(test.boundingbox); err != nil { + if err != test.Exception { + t.Errorf("test: %d, expected: %+v \ngot: %+v", k, test.Exception, err) + } + } else { + if bbox != test.bbox { + t.Errorf("test: %d, expected: %+v \ngot: %+v", k, test.bbox, bbox) + } + } + } +} + func TestStripDuplicateAttr(t *testing.T) { var tests = []struct { attributes []xml.Attr diff --git a/pkg/wms130/request/getfeatureinfo.go b/pkg/wms130/request/getfeatureinfo.go index 94e75ba..fa19f18 100644 --- a/pkg/wms130/request/getfeatureinfo.go +++ b/pkg/wms130/request/getfeatureinfo.go @@ -105,8 +105,7 @@ func (gfi *GetFeatureInfo) ParseKVP(query url.Values) ows.Exception { gfi.CRS = query[k][0] case BBOX: var bbox ows.BoundingBox - var err ows.Exception - if bbox, err = buildBoundingBox(query[k][0]); err != nil { + if err := bbox.Build(query[k][0]); err != nil { return err } gfi.BoundingBox = bbox diff --git a/pkg/wms130/request/getmap.go b/pkg/wms130/request/getmap.go index 2df28cc..7760b0d 100644 --- a/pkg/wms130/request/getmap.go +++ b/pkg/wms130/request/getmap.go @@ -186,8 +186,8 @@ func (gm *GetMap) ParseGetMapKVP(gmkvp GetMapKVP) ows.Exception { gm.CRS = gmkvp.CRS - bbox, err := buildBoundingBox(gmkvp.Bbox) - if err != nil { + var bbox ows.BoundingBox + if err := bbox.Build(gmkvp.Bbox); err != nil { return err } gm.BoundingBox = bbox @@ -259,38 +259,38 @@ func (gm *GetMap) BuildXML() []byte { return append([]byte(xml.Header), si...) } -func buildBoundingBox(boundingbox string) (ows.BoundingBox, ows.Exception) { - result := strings.Split(boundingbox, ",") - var lx, ly, ux, uy float64 - var err error - - if len(result) < 4 { - return ows.BoundingBox{}, ows.InvalidParameterValue(boundingbox, BBOX) - } - - if len(result) == 4 || len(result) == 5 { - if lx, err = strconv.ParseFloat(result[0], 64); err != nil { - return ows.BoundingBox{}, ows.InvalidParameterValue(boundingbox, BBOX) - } - if ly, err = strconv.ParseFloat(result[1], 64); err != nil { - return ows.BoundingBox{}, ows.InvalidParameterValue(boundingbox, BBOX) - } - if ux, err = strconv.ParseFloat(result[2], 64); err != nil { - return ows.BoundingBox{}, ows.InvalidParameterValue(boundingbox, BBOX) - } - if uy, err = strconv.ParseFloat(result[3], 64); err != nil { - return ows.BoundingBox{}, ows.InvalidParameterValue(boundingbox, BBOX) - } - } - - if len(result) == 5 { - return ows.BoundingBox{LowerCorner: [2]float64{lx, ly}, - UpperCorner: [2]float64{ux, uy}, Crs: result[4]}, nil - } - - return ows.BoundingBox{LowerCorner: [2]float64{lx, ly}, - UpperCorner: [2]float64{ux, uy}}, nil -} +// func buildBoundingBox(boundingbox string) (ows.BoundingBox, ows.Exception) { +// result := strings.Split(boundingbox, ",") +// var lx, ly, ux, uy float64 +// var err error + +// if len(result) < 4 { +// return ows.BoundingBox{}, ows.InvalidParameterValue(boundingbox, BBOX) +// } + +// if len(result) == 4 || len(result) == 5 { +// if lx, err = strconv.ParseFloat(result[0], 64); err != nil { +// return ows.BoundingBox{}, ows.InvalidParameterValue(boundingbox, BBOX) +// } +// if ly, err = strconv.ParseFloat(result[1], 64); err != nil { +// return ows.BoundingBox{}, ows.InvalidParameterValue(boundingbox, BBOX) +// } +// if ux, err = strconv.ParseFloat(result[2], 64); err != nil { +// return ows.BoundingBox{}, ows.InvalidParameterValue(boundingbox, BBOX) +// } +// if uy, err = strconv.ParseFloat(result[3], 64); err != nil { +// return ows.BoundingBox{}, ows.InvalidParameterValue(boundingbox, BBOX) +// } +// } + +// if len(result) == 5 { +// return ows.BoundingBox{LowerCorner: [2]float64{lx, ly}, +// UpperCorner: [2]float64{ux, uy}, Crs: result[4]}, nil +// } + +// return ows.BoundingBox{LowerCorner: [2]float64{lx, ly}, +// UpperCorner: [2]float64{ux, uy}}, nil +// } func buildStyledLayerDescriptor(layers, styles []string) (StyledLayerDescriptor, ows.Exception) { // Because the LAYERS & STYLES parameters are intertwined we process as follows: diff --git a/pkg/wms130/request/getmap_test.go b/pkg/wms130/request/getmap_test.go index 5b68cf6..874a1e5 100644 --- a/pkg/wms130/request/getmap_test.go +++ b/pkg/wms130/request/getmap_test.go @@ -24,33 +24,6 @@ func TestGetMapType(t *testing.T) { } } -func TestBuildBoundingBox(t *testing.T) { - var tests = []struct { - boundingbox string - bbox ows.BoundingBox - Exception ows.Exception - }{ - 0: {boundingbox: "0,0,100,100", bbox: ows.BoundingBox{LowerCorner: [2]float64{0, 0}, UpperCorner: [2]float64{100, 100}}}, - 1: {boundingbox: "0,0,-100,-100", bbox: ows.BoundingBox{LowerCorner: [2]float64{0, 0}, UpperCorner: [2]float64{-100, -100}}}, // while this isn't correct, this will be 'addressed' in the validation step - 2: {boundingbox: "0,0,100", Exception: ows.InvalidParameterValue(`0,0,100`, BBOX)}, - 3: {boundingbox: ",,,", Exception: ows.InvalidParameterValue(`,,,`, BBOX)}, - 4: {boundingbox: ",,,100", Exception: ows.InvalidParameterValue(`,,,100`, BBOX)}, - 5: {boundingbox: "number,,,100", Exception: ows.InvalidParameterValue(`number,,,100`, BBOX)}, - } - - for k, test := range tests { - if bbox, err := buildBoundingBox(test.boundingbox); err != nil { - if err != test.Exception { - t.Errorf("test: %d, expected: %+v \ngot: %+v", k, test.Exception, err) - } - } else { - if bbox != test.bbox { - t.Errorf("test: %d, expected: %+v \ngot: %+v", k, test.bbox, bbox) - } - } - } -} - func TestBuildStyledLayerDescriptor(t *testing.T) { var tests = []struct { layers []string @@ -249,7 +222,7 @@ func TestGetMapParseKVP(t *testing.T) { Exception ows.Exception }{ 0: {Query: map[string][]string{REQUEST: {getmap}, SERVICE: {Service}, VERSION: {Version}}, - Exception: ows.InvalidParameterValue(``, BBOX), + Exception: ows.InvalidParameterValue(``, `boundingbox`), }, 1: {Query: url.Values{}, Exception: ows.MissingParameterValue(VERSION)}, From a0483f9b059ce2fce6ad059cb6a65964d3891ae4 Mon Sep 17 00:00:00 2001 From: Wouter Date: Mon, 27 Jul 2020 21:54:48 +0200 Subject: [PATCH 09/45] init getmap layer validation --- pkg/wms130/capabilities/capabilities.go | 32 +++++++++++ pkg/wms130/capabilities/capabilities_test.go | 57 ++++++++++++++++++++ pkg/wms130/request/getmap.go | 56 ++++++++----------- pkg/wms130/request/getmap_test.go | 37 +++++++++++++ 4 files changed, 149 insertions(+), 33 deletions(-) create mode 100644 pkg/wms130/capabilities/capabilities_test.go diff --git a/pkg/wms130/capabilities/capabilities.go b/pkg/wms130/capabilities/capabilities.go index d02f900..b6ced05 100644 --- a/pkg/wms130/capabilities/capabilities.go +++ b/pkg/wms130/capabilities/capabilities.go @@ -43,6 +43,38 @@ type Layer struct { Layer []*Layer `xml:"Layer" yaml:"layer"` } +// GetLayerNames returns the available layers as []string +func (c *Capability) GetLayerNames() []string { + var layers []string + + for _, l := range c.Layer { + layers = append(layers, *l.Name) + if l.Layer != nil { + for _, n := range l.Layer { + u := n.GetLayerNames() + layers = append(layers, u...) + } + } + } + + return layers +} + +// GetLayerNames returns the available layers as []string +func (l *Layer) GetLayerNames() []string { + var layers []string + + layers = append(layers, *l.Name) + if l.Layer != nil { + for _, n := range l.Layer { + u := n.GetLayerNames() + layers = append(layers, u...) + } + } + + return layers +} + // RequestType containing the formats and DCPTypes available type RequestType struct { Format []string `xml:"Format" yaml:"format"` diff --git a/pkg/wms130/capabilities/capabilities_test.go b/pkg/wms130/capabilities/capabilities_test.go new file mode 100644 index 0000000..369b125 --- /dev/null +++ b/pkg/wms130/capabilities/capabilities_test.go @@ -0,0 +1,57 @@ +package capabilities + +import ( + "testing" +) + +func sp(s string) *string { + return &s +} + +func TestGetLayerNames(t *testing.T) { + capabilities := Capability{ + Layer: []Layer{ + { + Name: sp(`depthOneLayerOne`), + Layer: []*Layer{ + { + Name: sp(`depthTwoLayerThree`), + }, + { + Name: sp(`depthTwoLayerFour`), + Layer: []*Layer{ + { + Name: sp(`depthThreeLayerSix`), + }, + { + Name: sp(`depthThreeLayerSeven`), + }, + }, + }, + }, + }, + { + Name: sp(`depthOneLayerTwo`), + Layer: []*Layer{ + { + Name: sp(`depthTwoLayerFive`), + }, + }, + }, + }, + } + + expected := []string{`depthOneLayerOne`, `depthOneLayerTwo`, `depthTwoLayerThree`, `depthTwoLayerFour`, `depthTwoLayerFive`, `depthThreeLayerSix`, `depthThreeLayerSeven`} + + for _, n := range capabilities.GetLayerNames() { + found := false + for _, e := range expected { + if n == e { + found = true + } + } + if !found { + t.Errorf(" got: %s", n) + } + } +} diff --git a/pkg/wms130/request/getmap.go b/pkg/wms130/request/getmap.go index 7760b0d..aaa425f 100644 --- a/pkg/wms130/request/getmap.go +++ b/pkg/wms130/request/getmap.go @@ -9,6 +9,7 @@ import ( "strings" "github.com/pdok/ogc-specifications/pkg/ows" + "github.com/pdok/ogc-specifications/pkg/wms130/capabilities" "github.com/pdok/ogc-specifications/pkg/wms130/exception" "gopkg.in/yaml.v2" ) @@ -259,39 +260,6 @@ func (gm *GetMap) BuildXML() []byte { return append([]byte(xml.Header), si...) } -// func buildBoundingBox(boundingbox string) (ows.BoundingBox, ows.Exception) { -// result := strings.Split(boundingbox, ",") -// var lx, ly, ux, uy float64 -// var err error - -// if len(result) < 4 { -// return ows.BoundingBox{}, ows.InvalidParameterValue(boundingbox, BBOX) -// } - -// if len(result) == 4 || len(result) == 5 { -// if lx, err = strconv.ParseFloat(result[0], 64); err != nil { -// return ows.BoundingBox{}, ows.InvalidParameterValue(boundingbox, BBOX) -// } -// if ly, err = strconv.ParseFloat(result[1], 64); err != nil { -// return ows.BoundingBox{}, ows.InvalidParameterValue(boundingbox, BBOX) -// } -// if ux, err = strconv.ParseFloat(result[2], 64); err != nil { -// return ows.BoundingBox{}, ows.InvalidParameterValue(boundingbox, BBOX) -// } -// if uy, err = strconv.ParseFloat(result[3], 64); err != nil { -// return ows.BoundingBox{}, ows.InvalidParameterValue(boundingbox, BBOX) -// } -// } - -// if len(result) == 5 { -// return ows.BoundingBox{LowerCorner: [2]float64{lx, ly}, -// UpperCorner: [2]float64{ux, uy}, Crs: result[4]}, nil -// } - -// return ows.BoundingBox{LowerCorner: [2]float64{lx, ly}, -// UpperCorner: [2]float64{ux, uy}}, nil -// } - func buildStyledLayerDescriptor(layers, styles []string) (StyledLayerDescriptor, ows.Exception) { // Because the LAYERS & STYLES parameters are intertwined we process as follows: // 1. cnt(STYLE) == 0 -> Added LAYERS @@ -405,6 +373,28 @@ type StyledLayerDescriptor struct { NamedLayer []NamedLayer `xml:"NamedLayer" yaml:"namedlayer" validate:"required"` } +// Validate the StyledLayerDescriptor +func (sld StyledLayerDescriptor) Validate(capabilities *capabilities.Capability) ows.Exception { + var unknown []string + for _, l := range sld.GetNamedLayers() { + found := false + for _, c := range capabilities.GetLayerNames() { + if l == c { + found = true + } + } + if !found { + unknown = append(unknown, l) + } + } + + if len(unknown) > 0 { + return exception.LayerNotDefined(unknown...) + } + + return nil +} + // NamedLayer struct type NamedLayer struct { Name string `xml:"Name" yaml:"name" validate:"required"` diff --git a/pkg/wms130/request/getmap_test.go b/pkg/wms130/request/getmap_test.go index 874a1e5..91531a2 100644 --- a/pkg/wms130/request/getmap_test.go +++ b/pkg/wms130/request/getmap_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/pdok/ogc-specifications/pkg/ows" + "github.com/pdok/ogc-specifications/pkg/wms130/capabilities" "github.com/pdok/ogc-specifications/pkg/wms130/exception" ) @@ -61,6 +62,42 @@ func TestBuildStyledLayerDescriptor(t *testing.T) { } } +func TestValidateStyledLayerDescriptor(t *testing.T) { + var tests = []struct { + capabilities *capabilities.Capability + sld StyledLayerDescriptor + exception ows.Exception + }{ + 0: { + capabilities: &capabilities.Capability{ + Layer: []capabilities.Layer{ + {Name: sp(`layer1`)}, + {Name: sp(`layer2`)}, + }, + }, + sld: StyledLayerDescriptor{NamedLayer: []NamedLayer{{Name: "layer1"}, {Name: "layer2"}}}, + }, + 1: { + capabilities: &capabilities.Capability{ + Layer: []capabilities.Layer{ + {Name: sp(`layer2`)}, + {Name: sp(`layer3`)}, + }, + }, + sld: StyledLayerDescriptor{NamedLayer: []NamedLayer{{Name: "layer1"}, {Name: "layer2"}}}, + exception: exception.LayerNotDefined(`layer1`), + }, + } + + for k, test := range tests { + err := test.sld.Validate(test.capabilities) + if test.exception != err { + t.Errorf("test exception: %d, expected: %s ,\n got: %s", k, test.exception.Error(), err.Error()) + } + + } +} + func TestGetMapParseXML(t *testing.T) { var tests = []struct { Body []byte From f9be0f997821a08ca760b261eabcc2e2e08dfd94 Mon Sep 17 00:00:00 2001 From: Wouter Date: Mon, 27 Jul 2020 22:09:27 +0200 Subject: [PATCH 10/45] usage of capabilities spec instead of response.GetCapabilities --- pkg/wms130/validation/validations.go | 2 +- pkg/wms130/validation/validator_test.go | 16 ++++++---------- pkg/wms130/validation/wrappers.go | 6 +++--- 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/pkg/wms130/validation/validations.go b/pkg/wms130/validation/validations.go index 6b85f3a..f24706d 100644 --- a/pkg/wms130/validation/validations.go +++ b/pkg/wms130/validation/validations.go @@ -34,7 +34,7 @@ func GetMapValidation(sl validator.StructLevel) { // layer for _, sldname := range wr.getmap.StyledLayerDescriptor.GetNamedLayers() { known := false - for _, l := range wr.getcapabilities.Capability.Layer { + for _, l := range wr.capabilities.Layer { if *l.Name == sldname { known = true } diff --git a/pkg/wms130/validation/validator_test.go b/pkg/wms130/validation/validator_test.go index 3be4ea5..ec795c4 100644 --- a/pkg/wms130/validation/validator_test.go +++ b/pkg/wms130/validation/validator_test.go @@ -11,7 +11,6 @@ import ( "github.com/pdok/ogc-specifications/pkg/ows" "github.com/pdok/ogc-specifications/pkg/wms130/capabilities" "github.com/pdok/ogc-specifications/pkg/wms130/request" - "github.com/pdok/ogc-specifications/pkg/wms130/response" ) func sp(s string) *string { @@ -30,14 +29,11 @@ func TestValidation(t *testing.T) { registerValidations(v) registerTranslations(v, &trans) - getcapabilities := response.GetCapabilities{ - WMSService: response.WMSService{Name: "RiversRoadsAndHouses"}, - Capability: capabilities.Capability{ - Layer: []capabilities.Layer{ - {Name: sp(`Rivers`), CRS: []*string{sp(`EPSG:4326`)}}, - {Name: sp(`Roads`), CRS: []*string{sp(`EPSG:4326`)}}, - {Name: sp(`Houses`), CRS: []*string{sp(`EPSG:4326`)}}, - }, + capabilities := capabilities.Capability{ + Layer: []capabilities.Layer{ + {Name: sp(`Rivers`), CRS: []*string{sp(`EPSG:4326`)}}, + {Name: sp(`Roads`), CRS: []*string{sp(`EPSG:4326`)}}, + {Name: sp(`Houses`), CRS: []*string{sp(`EPSG:4326`)}}, }, } @@ -74,7 +70,7 @@ func TestValidation(t *testing.T) { for k, n := range tests { - wr := GetMapWrapper{getcapabilities: &getcapabilities, getmap: &n.Object} + wr := GetMapWrapper{capabilities: &capabilities, getmap: &n.Object} err := v.Struct(wr) if err != nil { diff --git a/pkg/wms130/validation/wrappers.go b/pkg/wms130/validation/wrappers.go index e41e193..77ed0c5 100644 --- a/pkg/wms130/validation/wrappers.go +++ b/pkg/wms130/validation/wrappers.go @@ -1,12 +1,12 @@ package validation import ( + "github.com/pdok/ogc-specifications/pkg/wms130/capabilities" "github.com/pdok/ogc-specifications/pkg/wms130/request" - "github.com/pdok/ogc-specifications/pkg/wms130/response" ) // GetMapWrapper struct type GetMapWrapper struct { - getcapabilities *response.GetCapabilities - getmap *request.GetMap + capabilities *capabilities.Capability + getmap *request.GetMap } From 76acc4fa2f103e1070c0b872aea7d6346367bd98 Mon Sep 17 00:00:00 2001 From: Wouter Date: Tue, 28 Jul 2020 20:19:08 +0200 Subject: [PATCH 11/45] input from pointer->value, pointer isn't faster, so for simplicity back to values --- pkg/ows/operationrequest.go | 36 +- pkg/ows/operationresponse.go | 28 +- pkg/wcs201/request/getcapabilities.go | 206 +-- pkg/wcs201/response/getcapabilities.go | 254 ++-- pkg/wfs200/request/common.go | 90 +- pkg/wfs200/request/describefeaturetype.go | 240 ++-- pkg/wfs200/request/getcapabilities.go | 184 +-- pkg/wfs200/request/getfeature.go | 1450 ++++++++++----------- pkg/wfs200/response/getcapabilities.go | 252 ++-- pkg/wms130/request/getmap.go | 2 +- pkg/wms130/request/getmap_test.go | 1276 +++++++++--------- 11 files changed, 2009 insertions(+), 2009 deletions(-) diff --git a/pkg/ows/operationrequest.go b/pkg/ows/operationrequest.go index e2914ca..5388019 100644 --- a/pkg/ows/operationrequest.go +++ b/pkg/ows/operationrequest.go @@ -1,18 +1,18 @@ -package ows - -import "net/url" - -// OperationRequest interface -type OperationRequest interface { - Type() string - Validate() Exception - - ParseXML([]byte) Exception - ParseKVP(url.Values) Exception - BuildXML() []byte - BuildKVP() url.Values - - // TODO YAML support - // ParseYAML([]byte) Exception - // BuildYAML() []byte -} +package ows + +import "net/url" + +// OperationRequest interface +type OperationRequest interface { + Type() string + Validate() Exception + + ParseXML([]byte) Exception + ParseKVP(url.Values) Exception + BuildXML() []byte + BuildKVP() url.Values + + // TODO YAML support + // ParseYAML([]byte) Exception + // BuildYAML() []byte +} diff --git a/pkg/ows/operationresponse.go b/pkg/ows/operationresponse.go index 2a8d5ee..7934a09 100644 --- a/pkg/ows/operationresponse.go +++ b/pkg/ows/operationresponse.go @@ -1,14 +1,14 @@ -package ows - -// OperationResponse interface -type OperationResponse interface { - Type() string - Service() string - Version() string - Validate() Exception - - // ParseXML([]byte) Exception - ParseYAML([]byte) Exception - BuildXML() []byte - BuildYAML() []byte -} +package ows + +// OperationResponse interface +type OperationResponse interface { + Type() string + Service() string + Version() string + Validate() Exception + + // ParseXML([]byte) Exception + ParseYAML([]byte) Exception + BuildXML() []byte + BuildYAML() []byte +} diff --git a/pkg/wcs201/request/getcapabilities.go b/pkg/wcs201/request/getcapabilities.go index 8dba0a1..51dca90 100644 --- a/pkg/wcs201/request/getcapabilities.go +++ b/pkg/wcs201/request/getcapabilities.go @@ -1,103 +1,103 @@ -package request - -import ( - "encoding/xml" - "net/url" - "regexp" - "strings" - - "github.com/pdok/ogc-specifications/pkg/ows" -) - -// -const ( - getcapabilities = `GetCapabilities` -) - -// Type and Version as constant -const ( - Service string = `WCS` - Version string = `2.0.1` -) - -// WCS 2.0.1 Tokens -const ( - SERVICE = `SERVICE` - REQUEST = `REQUEST` - VERSION = `VERSION` -) - -// Type returns GetCapabilities -func (gc *GetCapabilities) Type() string { - return getcapabilities -} - -// Validate returns GetCapabilities -func (gc *GetCapabilities) Validate() ows.Exception { - return nil -} - -// ParseXML builds a GetCapabilities object based on a XML document -func (gc *GetCapabilities) ParseXML(body []byte) ows.Exception { - var xmlattributes ows.XMLAttribute - if err := xml.Unmarshal(body, &xmlattributes); err != nil { - return ows.MissingParameterValue() - } - if err := xml.Unmarshal(body, &gc); err != nil { - return ows.MissingParameterValue("REQUEST") - } - var n []xml.Attr - for _, a := range xmlattributes { - switch strings.ToUpper(a.Name.Local) { - case VERSION: - case SERVICE: - default: - n = append(n, a) - } - } - - gc.Attr = ows.StripDuplicateAttr(n) - return nil -} - -// ParseKVP builds a GetCapabilities object based on the available query parameters -func (gc *GetCapabilities) ParseKVP(query url.Values) ows.Exception { - for k, v := range query { - switch strings.ToUpper(k) { - case REQUEST: - if strings.ToUpper(v[0]) == strings.ToUpper(getcapabilities) { - gc.XMLName.Local = getcapabilities - } - case SERVICE: - gc.Service = strings.ToUpper(v[0]) - case VERSION: - gc.Version = strings.ToUpper(v[0]) - } - } - return nil -} - -// BuildKVP builds a new query string that will be proxied -func (gc *GetCapabilities) BuildKVP() url.Values { - querystring := make(map[string][]string) - querystring[REQUEST] = []string{gc.XMLName.Local} - querystring[SERVICE] = []string{gc.Service} - querystring[VERSION] = []string{gc.Version} - - return querystring -} - -// BuildXML builds a 'new' XML document 'based' on the 'original' XML document -func (gc *GetCapabilities) BuildXML() []byte { - si, _ := xml.MarshalIndent(gc, "", "") - re := regexp.MustCompile(`><.*>`) - return []byte(xml.Header + re.ReplaceAllString(string(si), "/>")) -} - -// GetCapabilities struct with the needed parameters/attributes needed for making a GetCapabilities request -type GetCapabilities struct { - XMLName xml.Name `xml:"GetCapabilities" yaml:"getcapabilities" validate:"required"` - Service string `xml:"service,attr" yaml:"service" validate:"required,oneof=WCS wcs"` - Version string `xml:"version,attr" yaml:"version" validate:"eq=2.0.1"` - Attr ows.XMLAttribute `xml:",attr"` -} +package request + +import ( + "encoding/xml" + "net/url" + "regexp" + "strings" + + "github.com/pdok/ogc-specifications/pkg/ows" +) + +// +const ( + getcapabilities = `GetCapabilities` +) + +// Type and Version as constant +const ( + Service string = `WCS` + Version string = `2.0.1` +) + +// WCS 2.0.1 Tokens +const ( + SERVICE = `SERVICE` + REQUEST = `REQUEST` + VERSION = `VERSION` +) + +// Type returns GetCapabilities +func (gc *GetCapabilities) Type() string { + return getcapabilities +} + +// Validate returns GetCapabilities +func (gc *GetCapabilities) Validate() ows.Exception { + return nil +} + +// ParseXML builds a GetCapabilities object based on a XML document +func (gc *GetCapabilities) ParseXML(body []byte) ows.Exception { + var xmlattributes ows.XMLAttribute + if err := xml.Unmarshal(body, &xmlattributes); err != nil { + return ows.MissingParameterValue() + } + if err := xml.Unmarshal(body, &gc); err != nil { + return ows.MissingParameterValue("REQUEST") + } + var n []xml.Attr + for _, a := range xmlattributes { + switch strings.ToUpper(a.Name.Local) { + case VERSION: + case SERVICE: + default: + n = append(n, a) + } + } + + gc.Attr = ows.StripDuplicateAttr(n) + return nil +} + +// ParseKVP builds a GetCapabilities object based on the available query parameters +func (gc *GetCapabilities) ParseKVP(query url.Values) ows.Exception { + for k, v := range query { + switch strings.ToUpper(k) { + case REQUEST: + if strings.ToUpper(v[0]) == strings.ToUpper(getcapabilities) { + gc.XMLName.Local = getcapabilities + } + case SERVICE: + gc.Service = strings.ToUpper(v[0]) + case VERSION: + gc.Version = strings.ToUpper(v[0]) + } + } + return nil +} + +// BuildKVP builds a new query string that will be proxied +func (gc *GetCapabilities) BuildKVP() url.Values { + querystring := make(map[string][]string) + querystring[REQUEST] = []string{gc.XMLName.Local} + querystring[SERVICE] = []string{gc.Service} + querystring[VERSION] = []string{gc.Version} + + return querystring +} + +// BuildXML builds a 'new' XML document 'based' on the 'original' XML document +func (gc *GetCapabilities) BuildXML() []byte { + si, _ := xml.MarshalIndent(gc, "", "") + re := regexp.MustCompile(`><.*>`) + return []byte(xml.Header + re.ReplaceAllString(string(si), "/>")) +} + +// GetCapabilities struct with the needed parameters/attributes needed for making a GetCapabilities request +type GetCapabilities struct { + XMLName xml.Name `xml:"GetCapabilities" yaml:"getcapabilities" validate:"required"` + Service string `xml:"service,attr" yaml:"service" validate:"required,oneof=WCS wcs"` + Version string `xml:"version,attr" yaml:"version" validate:"eq=2.0.1"` + Attr ows.XMLAttribute `xml:",attr"` +} diff --git a/pkg/wcs201/response/getcapabilities.go b/pkg/wcs201/response/getcapabilities.go index 434aafe..8b3af0f 100644 --- a/pkg/wcs201/response/getcapabilities.go +++ b/pkg/wcs201/response/getcapabilities.go @@ -1,127 +1,127 @@ -package wcs201 - -import ( - "encoding/xml" - "regexp" - - "github.com/pdok/ogc-specifications/pkg/ows" - "github.com/pdok/ogc-specifications/pkg/wcs201/capabilities" -) - -// -const ( - getcapabilities = `GetCapabilities` -) - -// -const ( - Service = `WFS` - Version = `2.0.0` -) - -// Contains the WFS200 struct - -// Type function needed for the interface -func (gc *GetCapabilities) Type() string { - return getcapabilities -} - -// Service function needed for the interface -func (gc *GetCapabilities) Service() string { - return Service -} - -// Version function needed for the interface -func (gc *GetCapabilities) Version() string { - return Version -} - -// Validate function of the wfs200 spec -func (gc *GetCapabilities) Validate() ows.Exception { - return nil -} - -// BuildXML builds a GetCapabilities response object -func (gc *GetCapabilities) BuildXML() []byte { - si, _ := xml.MarshalIndent(gc, "", "") - re := regexp.MustCompile(`><.*>`) - return []byte(xml.Header + re.ReplaceAllString(string(si), "/>")) -} - -// GetCapabilities base struct -type GetCapabilities struct { - XMLName xml.Name `xml:"wcs:Capabilities"` - Namespaces `yaml:"namespaces"` - ServiceIdentification ServiceIdentification `xml:"ows:ServiceIdentification" yaml:"serviceidentification"` - ServiceProvider ServiceProvider `xml:"ows:ServiceProvider" yaml:"serviceprovider"` - OperationsMetadata capabilities.OperationsMetadata `xml:"ows:OperationsMetadata" yaml:"operationsmetadata"` - ServiceMetadata capabilities.ServiceMetadata `xml:"wcs:ServiceMetadata" yaml:"servicemetadata"` - Contents capabilities.Contents `xml:"wcs:Contents" yaml:"contents"` -} - -// Namespaces struct containing the namespaces needed for the XML document -type Namespaces struct { - XmlnsWCS string `xml:"xmlns:wcs,attr" yaml:"wcs"` //http://www.opengis.net/wcs/2.0 - XmlnsOWS string `xml:"xmlns:ows,attr" yaml:"ows"` //http://www.opengis.net/ows/1.1 - XmlnsOGC string `xml:"xmlns:ogc,attr" yaml:"ogc"` //http://www.opengis.net/ogc - XmlnsXSI string `xml:"xmlns:xsi,attr" yaml:"xsi"` //http://www.w3.org/2001/XMLSchema-instance - XmlnsXlink string `xml:"xmlns:xlink,attr" yaml:"xlink"` //http://www.w3.org/1999/xlink - XmlnsGML string `xml:"xmlns:gml,attr" yaml:"gml"` //http://www.opengis.net/gml/3.2 - XmlnsGMLcov string `xml:"xmlns:gmlcov,attr" yaml:"gmlcov"` //http://www.opengis.net/gmlcov/1.0 - XmlnsSWE string `xml:"xmlns:swe,attr" yaml:"swe"` //http://www.opengis.net/swe/2.0 - XmlnsInspireCommon string `xml:"xmlns:inspire_common,attr,omitempty" yaml:"inspirecommon"` //http://inspire.ec.europa.eu/schemas/common/1.0 - XmlnsInspireDls string `xml:"xmlns:inspire_dls,attr,omitempty" yaml:"inspiredls"` //http://inspire.ec.europa.eu/schemas/inspire_dls/1.0 - XmlnsCrs string `xml:"xmlns:crs,attr" yaml:"crs"` //http://www.opengis.net/wcs/crs/1.0 - XmlnsInt string `xml:"xmlns:int,attr" yaml:"int"` //http://www.opengis.net/wcs/interpolation/1.0 - Version string `xml:"version,attr" yaml:"version"` - SchemaLocation string `xml:"xsi:schemaLocation,attr" yaml:"schemalocation"` -} - -// ServiceIdentification struct should only be fill by the "template" configuration wcs201.yaml -type ServiceIdentification struct { - Title string `xml:"ows:Title" yaml:"title"` - Abstract string `xml:"ows:Abstract" yaml:"abstract"` - Keywords *ows.Keywords `xml:"ows:Keywords" yaml:"keywords"` - ServiceType struct { - Text string `xml:",chardata" yaml:"text"` - CodeSpace string `xml:"codeSpace,attr" yaml:"codespace"` - } `xml:"ows:ServiceType" yaml:"servicetype"` - ServiceTypeVersion []string `xml:"ows:ServiceTypeVersion" yaml:"servicetypeversion"` - Profile []string `xml:"ows:Profile" yaml:"profile"` - Fees string `xml:"ows:Fees" yaml:"fees"` - AccessConstraints string `xml:"ows:AccessConstraints" yaml:"accessconstraints"` -} - -// ServiceProvider struct containing the provider/organization information should only be fill by the "template" configuration wcs201.yaml -type ServiceProvider struct { - ProviderName string `xml:"ows:ProviderName" yaml:"providername"` - ProviderSite struct { - Type string `xml:"xlink:type,attr" yaml:"type"` - Href string `xml:"xlink:href,attr" yaml:"href"` - } `xml:"ows:ProviderSite" yaml:"providersite"` - ServiceContact struct { - IndividualName string `xml:"ows:IndividualName" yaml:"individualname"` - PositionName string `xml:"ows:PositionName" yaml:"positionname"` - ContactInfo struct { - Phone struct { - Voice string `xml:"ows:Voice" yaml:"voice"` - Facsimile string `xml:"ows:Facsimile" yaml:"facsimile"` - } `xml:"ows:Phone" yaml:"phone"` - Address struct { - DeliveryPoint string `xml:"ows:DeliveryPoint" yaml:"deliverypoint"` - City string `xml:"ows:City" yaml:"city"` - AdministrativeArea string `xml:"ows:AdministrativeArea" yaml:"administrativearea"` - PostalCode string `xml:"ows:PostalCode" yaml:"postalcode"` - Country string `xml:"ows:Country" yaml:"country"` - ElectronicMailAddress string `xml:"ows:ElectronicMailAddress" yaml:"electronicmailaddress"` - } `xml:"ows:Address" yaml:"address"` - OnlineResource struct { - Type string `xml:"xlink:type,attr" yaml:"type"` - Href string `xml:"xlink:href,attr" yaml:"href"` - } `xml:"ows:OnlineResource" yaml:"onlineresource"` - HoursOfService string `xml:"ows:HoursOfService" yaml:"hoursofservice"` - ContactInstructions string `xml:"ows:ContactInstructions" yaml:"contactinstructions"` - } `xml:"ows:ContactInfo" yaml:"contactinfo"` - Role string `xml:"ows:Role" yaml:"role"` - } `xml:"ows:ServiceContact" yaml:"servicecontact"` -} +package wcs201 + +import ( + "encoding/xml" + "regexp" + + "github.com/pdok/ogc-specifications/pkg/ows" + "github.com/pdok/ogc-specifications/pkg/wcs201/capabilities" +) + +// +const ( + getcapabilities = `GetCapabilities` +) + +// +const ( + Service = `WFS` + Version = `2.0.0` +) + +// Contains the WFS200 struct + +// Type function needed for the interface +func (gc *GetCapabilities) Type() string { + return getcapabilities +} + +// Service function needed for the interface +func (gc *GetCapabilities) Service() string { + return Service +} + +// Version function needed for the interface +func (gc *GetCapabilities) Version() string { + return Version +} + +// Validate function of the wfs200 spec +func (gc *GetCapabilities) Validate() ows.Exception { + return nil +} + +// BuildXML builds a GetCapabilities response object +func (gc *GetCapabilities) BuildXML() []byte { + si, _ := xml.MarshalIndent(gc, "", "") + re := regexp.MustCompile(`><.*>`) + return []byte(xml.Header + re.ReplaceAllString(string(si), "/>")) +} + +// GetCapabilities base struct +type GetCapabilities struct { + XMLName xml.Name `xml:"wcs:Capabilities"` + Namespaces `yaml:"namespaces"` + ServiceIdentification ServiceIdentification `xml:"ows:ServiceIdentification" yaml:"serviceidentification"` + ServiceProvider ServiceProvider `xml:"ows:ServiceProvider" yaml:"serviceprovider"` + OperationsMetadata capabilities.OperationsMetadata `xml:"ows:OperationsMetadata" yaml:"operationsmetadata"` + ServiceMetadata capabilities.ServiceMetadata `xml:"wcs:ServiceMetadata" yaml:"servicemetadata"` + Contents capabilities.Contents `xml:"wcs:Contents" yaml:"contents"` +} + +// Namespaces struct containing the namespaces needed for the XML document +type Namespaces struct { + XmlnsWCS string `xml:"xmlns:wcs,attr" yaml:"wcs"` //http://www.opengis.net/wcs/2.0 + XmlnsOWS string `xml:"xmlns:ows,attr" yaml:"ows"` //http://www.opengis.net/ows/1.1 + XmlnsOGC string `xml:"xmlns:ogc,attr" yaml:"ogc"` //http://www.opengis.net/ogc + XmlnsXSI string `xml:"xmlns:xsi,attr" yaml:"xsi"` //http://www.w3.org/2001/XMLSchema-instance + XmlnsXlink string `xml:"xmlns:xlink,attr" yaml:"xlink"` //http://www.w3.org/1999/xlink + XmlnsGML string `xml:"xmlns:gml,attr" yaml:"gml"` //http://www.opengis.net/gml/3.2 + XmlnsGMLcov string `xml:"xmlns:gmlcov,attr" yaml:"gmlcov"` //http://www.opengis.net/gmlcov/1.0 + XmlnsSWE string `xml:"xmlns:swe,attr" yaml:"swe"` //http://www.opengis.net/swe/2.0 + XmlnsInspireCommon string `xml:"xmlns:inspire_common,attr,omitempty" yaml:"inspirecommon"` //http://inspire.ec.europa.eu/schemas/common/1.0 + XmlnsInspireDls string `xml:"xmlns:inspire_dls,attr,omitempty" yaml:"inspiredls"` //http://inspire.ec.europa.eu/schemas/inspire_dls/1.0 + XmlnsCrs string `xml:"xmlns:crs,attr" yaml:"crs"` //http://www.opengis.net/wcs/crs/1.0 + XmlnsInt string `xml:"xmlns:int,attr" yaml:"int"` //http://www.opengis.net/wcs/interpolation/1.0 + Version string `xml:"version,attr" yaml:"version"` + SchemaLocation string `xml:"xsi:schemaLocation,attr" yaml:"schemalocation"` +} + +// ServiceIdentification struct should only be fill by the "template" configuration wcs201.yaml +type ServiceIdentification struct { + Title string `xml:"ows:Title" yaml:"title"` + Abstract string `xml:"ows:Abstract" yaml:"abstract"` + Keywords *ows.Keywords `xml:"ows:Keywords" yaml:"keywords"` + ServiceType struct { + Text string `xml:",chardata" yaml:"text"` + CodeSpace string `xml:"codeSpace,attr" yaml:"codespace"` + } `xml:"ows:ServiceType" yaml:"servicetype"` + ServiceTypeVersion []string `xml:"ows:ServiceTypeVersion" yaml:"servicetypeversion"` + Profile []string `xml:"ows:Profile" yaml:"profile"` + Fees string `xml:"ows:Fees" yaml:"fees"` + AccessConstraints string `xml:"ows:AccessConstraints" yaml:"accessconstraints"` +} + +// ServiceProvider struct containing the provider/organization information should only be fill by the "template" configuration wcs201.yaml +type ServiceProvider struct { + ProviderName string `xml:"ows:ProviderName" yaml:"providername"` + ProviderSite struct { + Type string `xml:"xlink:type,attr" yaml:"type"` + Href string `xml:"xlink:href,attr" yaml:"href"` + } `xml:"ows:ProviderSite" yaml:"providersite"` + ServiceContact struct { + IndividualName string `xml:"ows:IndividualName" yaml:"individualname"` + PositionName string `xml:"ows:PositionName" yaml:"positionname"` + ContactInfo struct { + Phone struct { + Voice string `xml:"ows:Voice" yaml:"voice"` + Facsimile string `xml:"ows:Facsimile" yaml:"facsimile"` + } `xml:"ows:Phone" yaml:"phone"` + Address struct { + DeliveryPoint string `xml:"ows:DeliveryPoint" yaml:"deliverypoint"` + City string `xml:"ows:City" yaml:"city"` + AdministrativeArea string `xml:"ows:AdministrativeArea" yaml:"administrativearea"` + PostalCode string `xml:"ows:PostalCode" yaml:"postalcode"` + Country string `xml:"ows:Country" yaml:"country"` + ElectronicMailAddress string `xml:"ows:ElectronicMailAddress" yaml:"electronicmailaddress"` + } `xml:"ows:Address" yaml:"address"` + OnlineResource struct { + Type string `xml:"xlink:type,attr" yaml:"type"` + Href string `xml:"xlink:href,attr" yaml:"href"` + } `xml:"ows:OnlineResource" yaml:"onlineresource"` + HoursOfService string `xml:"ows:HoursOfService" yaml:"hoursofservice"` + ContactInstructions string `xml:"ows:ContactInstructions" yaml:"contactinstructions"` + } `xml:"ows:ContactInfo" yaml:"contactinfo"` + Role string `xml:"ows:Role" yaml:"role"` + } `xml:"ows:ServiceContact" yaml:"servicecontact"` +} diff --git a/pkg/wfs200/request/common.go b/pkg/wfs200/request/common.go index 0c18537..b602f53 100644 --- a/pkg/wfs200/request/common.go +++ b/pkg/wfs200/request/common.go @@ -1,45 +1,45 @@ -package request - -import ( - "net/url" - - "github.com/pdok/ogc-specifications/pkg/ows" -) - -// -const ( - Service = `WFS` - Version = `2.0.0` -) - -// WFS 2.0.0 Tokens -const ( - SERVICE = `SERVICE` - REQUEST = `REQUEST` - VERSION = `VERSION` - - OUTPUTFORMAT = `OUTPUTFORMAT` -) - -// BaseRequest based on Table 5 WFS2.0.0 spec -// Note: not usable for GetCapabilities request regarding deviation of Optional/Mandatory parameters SERVICE and VERSION -type BaseRequest struct { - Service string `xml:"service,attr" yaml:"service,omitempty" validate:"oneof=WFS wfs"` - Version string `xml:"version,attr" yaml:"version" validate:"required,eq=2.0.0"` - Attr ows.XMLAttribute `xml:",attr"` -} - -// ParseKVP builds a BaseRequest Struct based on the given parameters -func (b *BaseRequest) ParseKVP(query url.Values) ows.Exception { - if len(query[SERVICE]) > 0 { - // Service is optional, because it's implicit for a GetFeature/DescribeFeatureType request - b.Service = query[SERVICE][0] - } - if len(query[VERSION]) > 0 { - b.Version = query[VERSION][0] - } else { - // Version is mandatory - return ows.MissingParameterValue(VERSION) - } - return nil -} +package request + +import ( + "net/url" + + "github.com/pdok/ogc-specifications/pkg/ows" +) + +// +const ( + Service = `WFS` + Version = `2.0.0` +) + +// WFS 2.0.0 Tokens +const ( + SERVICE = `SERVICE` + REQUEST = `REQUEST` + VERSION = `VERSION` + + OUTPUTFORMAT = `OUTPUTFORMAT` +) + +// BaseRequest based on Table 5 WFS2.0.0 spec +// Note: not usable for GetCapabilities request regarding deviation of Optional/Mandatory parameters SERVICE and VERSION +type BaseRequest struct { + Service string `xml:"service,attr" yaml:"service,omitempty" validate:"oneof=WFS wfs"` + Version string `xml:"version,attr" yaml:"version" validate:"required,eq=2.0.0"` + Attr ows.XMLAttribute `xml:",attr"` +} + +// ParseKVP builds a BaseRequest Struct based on the given parameters +func (b *BaseRequest) ParseKVP(query url.Values) ows.Exception { + if len(query[SERVICE]) > 0 { + // Service is optional, because it's implicit for a GetFeature/DescribeFeatureType request + b.Service = query[SERVICE][0] + } + if len(query[VERSION]) > 0 { + b.Version = query[VERSION][0] + } else { + // Version is mandatory + return ows.MissingParameterValue(VERSION) + } + return nil +} diff --git a/pkg/wfs200/request/describefeaturetype.go b/pkg/wfs200/request/describefeaturetype.go index 331daa4..53b1958 100644 --- a/pkg/wfs200/request/describefeaturetype.go +++ b/pkg/wfs200/request/describefeaturetype.go @@ -1,120 +1,120 @@ -package request - -import ( - "encoding/xml" - "net/url" - - "github.com/pdok/ogc-specifications/pkg/ows" - "github.com/pdok/ogc-specifications/pkg/utils" - - "regexp" - "strings" -) - -// -const ( - TYPENAME = `TYPENAME` //NOTE: TYPENAME for KVP encoding & typeNames for XML encoding - describefeaturetype = `DescribeFeatureType` -) - -// Type returns DescribeFeatureType -func (dft *DescribeFeatureType) Type() string { - return describefeaturetype -} - -// Validate returns GetCapabilities -func (dft *DescribeFeatureType) Validate() ows.Exception { - return nil -} - -// ParseXML builds a DescribeFeatureType object based on a XML document -func (dft *DescribeFeatureType) ParseXML(doc []byte) ows.Exception { - var xmlattributes ows.XMLAttribute - if err := xml.Unmarshal(doc, &xmlattributes); err != nil { - return ows.NoApplicableCode("Could not process XML, is it XML?") - } - if err := xml.Unmarshal(doc, &dft); err != nil { - return ows.OperationNotSupported(err.Error()) - } - var n []xml.Attr - for _, a := range xmlattributes { - switch strings.ToUpper(a.Name.Local) { - case VERSION: - case SERVICE: - case TYPENAME: - default: - n = append(n, a) - } - } - - dft.Attr = ows.StripDuplicateAttr(n) - return nil -} - -// ParseKVP builds a DescribeFeatureType object based on the available query parameters -func (dft *DescribeFeatureType) ParseKVP(query url.Values) ows.Exception { - - if len(query) == 0 { - // When there are no query value we know that at least - // the manadorty VERSION parameter is missing. - return ows.MissingParameterValue(VERSION) - } - - q := utils.KeysToUpper(query) - - var br BaseRequest - if err := br.ParseKVP(q); err != nil { - return err - } - dft.BaseRequest = br - - for k, v := range query { - switch strings.ToUpper(k) { - case REQUEST: - if strings.ToUpper(v[0]) == strings.ToUpper(describefeaturetype) { - dft.XMLName.Local = describefeaturetype - } - case TYPENAME: - dft.BaseDescribeFeatureTypeRequest.TypeName = &v[0] //TODO maybe process as a comma separated list - case OUTPUTFORMAT: - // TODO nothing for now always assume the default text/xml; subtype=gml/3.2 - } - } - - return nil -} - -// BuildKVP builds a new query string that will be proxied -func (dft *DescribeFeatureType) BuildKVP() url.Values { - querystring := make(map[string][]string) - querystring[REQUEST] = []string{dft.XMLName.Local} - querystring[SERVICE] = []string{dft.BaseRequest.Service} - querystring[VERSION] = []string{dft.BaseRequest.Version} - if dft.BaseDescribeFeatureTypeRequest.TypeName != nil { - querystring[TYPENAME] = []string{*dft.BaseDescribeFeatureTypeRequest.TypeName} - } - if dft.BaseDescribeFeatureTypeRequest.OutputFormat != nil { - querystring[OUTPUTFORMAT] = []string{*dft.BaseDescribeFeatureTypeRequest.OutputFormat} - } - return querystring -} - -// BuildXML builds a 'new' XML document 'based' on the 'original' XML document -func (dft *DescribeFeatureType) BuildXML() []byte { - si, _ := xml.MarshalIndent(dft, "", "") - re := regexp.MustCompile(`><.*>`) - return []byte(xml.Header + re.ReplaceAllString(string(si), "/>")) -} - -// DescribeFeatureType struct with the needed parameters/attributes needed for making a DescribeFeatureType request -type DescribeFeatureType struct { - XMLName xml.Name `xml:"DescribeFeatureType" yaml:"describefeaturetype"` - BaseRequest - BaseDescribeFeatureTypeRequest -} - -// BaseDescribeFeatureTypeRequest struct used by GetFeature -type BaseDescribeFeatureTypeRequest struct { - OutputFormat *string `xml:"outputFormat,attr" yaml:"outputformat"` - TypeName *string `xml:"typeNames,attr" yaml:"typenames"` -} +package request + +import ( + "encoding/xml" + "net/url" + + "github.com/pdok/ogc-specifications/pkg/ows" + "github.com/pdok/ogc-specifications/pkg/utils" + + "regexp" + "strings" +) + +// +const ( + TYPENAME = `TYPENAME` //NOTE: TYPENAME for KVP encoding & typeNames for XML encoding + describefeaturetype = `DescribeFeatureType` +) + +// Type returns DescribeFeatureType +func (dft *DescribeFeatureType) Type() string { + return describefeaturetype +} + +// Validate returns GetCapabilities +func (dft *DescribeFeatureType) Validate() ows.Exception { + return nil +} + +// ParseXML builds a DescribeFeatureType object based on a XML document +func (dft *DescribeFeatureType) ParseXML(doc []byte) ows.Exception { + var xmlattributes ows.XMLAttribute + if err := xml.Unmarshal(doc, &xmlattributes); err != nil { + return ows.NoApplicableCode("Could not process XML, is it XML?") + } + if err := xml.Unmarshal(doc, &dft); err != nil { + return ows.OperationNotSupported(err.Error()) + } + var n []xml.Attr + for _, a := range xmlattributes { + switch strings.ToUpper(a.Name.Local) { + case VERSION: + case SERVICE: + case TYPENAME: + default: + n = append(n, a) + } + } + + dft.Attr = ows.StripDuplicateAttr(n) + return nil +} + +// ParseKVP builds a DescribeFeatureType object based on the available query parameters +func (dft *DescribeFeatureType) ParseKVP(query url.Values) ows.Exception { + + if len(query) == 0 { + // When there are no query value we know that at least + // the manadorty VERSION parameter is missing. + return ows.MissingParameterValue(VERSION) + } + + q := utils.KeysToUpper(query) + + var br BaseRequest + if err := br.ParseKVP(q); err != nil { + return err + } + dft.BaseRequest = br + + for k, v := range query { + switch strings.ToUpper(k) { + case REQUEST: + if strings.ToUpper(v[0]) == strings.ToUpper(describefeaturetype) { + dft.XMLName.Local = describefeaturetype + } + case TYPENAME: + dft.BaseDescribeFeatureTypeRequest.TypeName = &v[0] //TODO maybe process as a comma separated list + case OUTPUTFORMAT: + // TODO nothing for now always assume the default text/xml; subtype=gml/3.2 + } + } + + return nil +} + +// BuildKVP builds a new query string that will be proxied +func (dft *DescribeFeatureType) BuildKVP() url.Values { + querystring := make(map[string][]string) + querystring[REQUEST] = []string{dft.XMLName.Local} + querystring[SERVICE] = []string{dft.BaseRequest.Service} + querystring[VERSION] = []string{dft.BaseRequest.Version} + if dft.BaseDescribeFeatureTypeRequest.TypeName != nil { + querystring[TYPENAME] = []string{*dft.BaseDescribeFeatureTypeRequest.TypeName} + } + if dft.BaseDescribeFeatureTypeRequest.OutputFormat != nil { + querystring[OUTPUTFORMAT] = []string{*dft.BaseDescribeFeatureTypeRequest.OutputFormat} + } + return querystring +} + +// BuildXML builds a 'new' XML document 'based' on the 'original' XML document +func (dft *DescribeFeatureType) BuildXML() []byte { + si, _ := xml.MarshalIndent(dft, "", "") + re := regexp.MustCompile(`><.*>`) + return []byte(xml.Header + re.ReplaceAllString(string(si), "/>")) +} + +// DescribeFeatureType struct with the needed parameters/attributes needed for making a DescribeFeatureType request +type DescribeFeatureType struct { + XMLName xml.Name `xml:"DescribeFeatureType" yaml:"describefeaturetype"` + BaseRequest + BaseDescribeFeatureTypeRequest +} + +// BaseDescribeFeatureTypeRequest struct used by GetFeature +type BaseDescribeFeatureTypeRequest struct { + OutputFormat *string `xml:"outputFormat,attr" yaml:"outputformat"` + TypeName *string `xml:"typeNames,attr" yaml:"typenames"` +} diff --git a/pkg/wfs200/request/getcapabilities.go b/pkg/wfs200/request/getcapabilities.go index 6f8de43..b3aa965 100644 --- a/pkg/wfs200/request/getcapabilities.go +++ b/pkg/wfs200/request/getcapabilities.go @@ -1,92 +1,92 @@ -package request - -import ( - "encoding/xml" - "net/url" - "regexp" - "strings" - - "github.com/pdok/ogc-specifications/pkg/ows" -) - -// Type and Version as constant -const ( - getcapabilities = `GetCapabilities` -) - -// Contains the GetCapabilities struct and specific functions for building a GetCapabilities request - -// Type returns GetCapabilities -func (gc *GetCapabilities) Type() string { - return getcapabilities -} - -// Validate returns GetCapabilities -func (gc *GetCapabilities) Validate() ows.Exception { - return nil -} - -// ParseXML builds a GetCapabilities object based on a XML document -func (gc *GetCapabilities) ParseXML(doc []byte) ows.Exception { - var xmlattributes ows.XMLAttribute - if err := xml.Unmarshal(doc, &xmlattributes); err != nil { - return ows.NoApplicableCode("Could not process XML, is it XML?") - } - if err := xml.Unmarshal(doc, &gc); err != nil { - return ows.OperationNotSupported(err.Error()) //TODO Should be OperationParsingFailed - } - var n []xml.Attr - for _, a := range xmlattributes { - switch strings.ToUpper(a.Name.Local) { - case VERSION: - case SERVICE: - default: - n = append(n, a) - } - } - - gc.Attr = ows.StripDuplicateAttr(n) - return nil -} - -// ParseKVP builds a GetCapabilities object based on the available query parameters -func (gc *GetCapabilities) ParseKVP(query url.Values) ows.Exception { - for k, v := range query { - switch strings.ToUpper(k) { - case REQUEST: - if strings.ToUpper(v[0]) == strings.ToUpper(getcapabilities) { - gc.XMLName.Local = getcapabilities - } - case SERVICE: - gc.Service = strings.ToUpper(v[0]) - case VERSION: - gc.Version = strings.ToUpper(v[0]) - } - } - return nil -} - -// BuildKVP builds a new query string that will be proxied -func (gc *GetCapabilities) BuildKVP() url.Values { - querystring := make(map[string][]string) - querystring[REQUEST] = []string{gc.XMLName.Local} - querystring[SERVICE] = []string{gc.Service} - querystring[VERSION] = []string{gc.Version} - - return querystring -} - -// BuildXML builds a 'new' XML document 'based' on the 'original' XML document -func (gc *GetCapabilities) BuildXML() []byte { - si, _ := xml.MarshalIndent(gc, "", "") - re := regexp.MustCompile(`><.*>`) - return []byte(xml.Header + re.ReplaceAllString(string(si), "/>")) -} - -// GetCapabilities struct with the needed parameters/attributes needed for making a GetCapabilities request -type GetCapabilities struct { - XMLName xml.Name `xml:"GetCapabilities" yaml:"getcapabilities"` - Service string `xml:"service,attr" yaml:"service" validate:"required,oneof=WFS wfs"` - Version string `xml:"version,attr" yaml:"version" validate:"eq=2.0.0"` - Attr ows.XMLAttribute `xml:",attr"` -} +package request + +import ( + "encoding/xml" + "net/url" + "regexp" + "strings" + + "github.com/pdok/ogc-specifications/pkg/ows" +) + +// Type and Version as constant +const ( + getcapabilities = `GetCapabilities` +) + +// Contains the GetCapabilities struct and specific functions for building a GetCapabilities request + +// Type returns GetCapabilities +func (gc *GetCapabilities) Type() string { + return getcapabilities +} + +// Validate returns GetCapabilities +func (gc *GetCapabilities) Validate() ows.Exception { + return nil +} + +// ParseXML builds a GetCapabilities object based on a XML document +func (gc *GetCapabilities) ParseXML(doc []byte) ows.Exception { + var xmlattributes ows.XMLAttribute + if err := xml.Unmarshal(doc, &xmlattributes); err != nil { + return ows.NoApplicableCode("Could not process XML, is it XML?") + } + if err := xml.Unmarshal(doc, &gc); err != nil { + return ows.OperationNotSupported(err.Error()) //TODO Should be OperationParsingFailed + } + var n []xml.Attr + for _, a := range xmlattributes { + switch strings.ToUpper(a.Name.Local) { + case VERSION: + case SERVICE: + default: + n = append(n, a) + } + } + + gc.Attr = ows.StripDuplicateAttr(n) + return nil +} + +// ParseKVP builds a GetCapabilities object based on the available query parameters +func (gc *GetCapabilities) ParseKVP(query url.Values) ows.Exception { + for k, v := range query { + switch strings.ToUpper(k) { + case REQUEST: + if strings.ToUpper(v[0]) == strings.ToUpper(getcapabilities) { + gc.XMLName.Local = getcapabilities + } + case SERVICE: + gc.Service = strings.ToUpper(v[0]) + case VERSION: + gc.Version = strings.ToUpper(v[0]) + } + } + return nil +} + +// BuildKVP builds a new query string that will be proxied +func (gc *GetCapabilities) BuildKVP() url.Values { + querystring := make(map[string][]string) + querystring[REQUEST] = []string{gc.XMLName.Local} + querystring[SERVICE] = []string{gc.Service} + querystring[VERSION] = []string{gc.Version} + + return querystring +} + +// BuildXML builds a 'new' XML document 'based' on the 'original' XML document +func (gc *GetCapabilities) BuildXML() []byte { + si, _ := xml.MarshalIndent(gc, "", "") + re := regexp.MustCompile(`><.*>`) + return []byte(xml.Header + re.ReplaceAllString(string(si), "/>")) +} + +// GetCapabilities struct with the needed parameters/attributes needed for making a GetCapabilities request +type GetCapabilities struct { + XMLName xml.Name `xml:"GetCapabilities" yaml:"getcapabilities"` + Service string `xml:"service,attr" yaml:"service" validate:"required,oneof=WFS wfs"` + Version string `xml:"version,attr" yaml:"version" validate:"eq=2.0.0"` + Attr ows.XMLAttribute `xml:",attr"` +} diff --git a/pkg/wfs200/request/getfeature.go b/pkg/wfs200/request/getfeature.go index 588c484..2070534 100644 --- a/pkg/wfs200/request/getfeature.go +++ b/pkg/wfs200/request/getfeature.go @@ -1,725 +1,725 @@ -package request - -import ( - "encoding/xml" - "fmt" - "net/url" - "regexp" - "strconv" - "strings" - - "github.com/pdok/ogc-specifications/pkg/ows" - "github.com/pdok/ogc-specifications/pkg/utils" - "github.com/pdok/ogc-specifications/pkg/wfs200/exception" -) - -// Contains the GetFeature struct and specific functions for building a GetFeature request - -// -const ( - getfeature = `GetFeature` - - // table5 - STARTINDEX = `STARTINDEX` - COUNT = `COUNT` - RESULTTYPE = `RESULTTYPE` - // table6 - RESOLVE = `RESOLVE` - RESOLVEDEPTH = `RESOLVEDEPTH` - RESOLVETIMEOUT = `RESOLVETIMEOUT` - // table7 - NAMESPACES = `NAMESPACES` - // table8 - TYPENAMES = `TYPENAMES` - ALIASES = `ALIASES` - SRSNAME = `SRSNAME` - FILTER = `FILTER` - FILTERLANGUAGE = `FILTER_LANGUAGE` - RESOURCEID = `RESOURCEID` - BBOX = `BBOX` // OGC 06-121r3 - SORTBY = `SORTBY` - // table10 - STOREDQUERYID = `STOREDQUERY_ID` -) - -// Type returns GetFeature -func (gf *GetFeature) Type() string { - return getfeature -} - -// Validate returns GetFeature -func (gf *GetFeature) Validate() ows.Exception { - return nil -} - -// WFS tables as map[string]bool, where the key (string) is the TOKEN and the bool if its a mandatory (true) or optional (false) attribute -var table5 = map[string]bool{STARTINDEX: false, COUNT: false, OUTPUTFORMAT: false, RESULTTYPE: false} - -//var table6 = map[string]bool{RESOLVE: false, RESOLVEDEPTH: false, RESOLVETIMEOUT: false} -var table7 = map[string]bool{NAMESPACES: false} //VSPs (<- vendor specific parameters) -var table8 = map[string]bool{TYPENAMES: true, ALIASES: false, SRSNAME: false, FILTER: false, FILTERLANGUAGE: false, RESOURCEID: false, BBOX: false, SORTBY: false} - -//var table10 = map[string]bool{STOREDQUERYID: true} //storedquery_parameter=value - -// ParseXML builds a GetCapabilities object based on a XML document -func (gf *GetFeature) ParseXML(doc []byte) ows.Exception { - var xmlattributes ows.XMLAttribute - if err := xml.Unmarshal(doc, &xmlattributes); err != nil { - return ows.NoApplicableCode("Could not process XML, is it XML?") - } - xml.Unmarshal(doc, &gf) //When object can be Unmarshalled -> XMLAttributes, it can be Unmarshalled -> GetFeature - var n []xml.Attr - for _, a := range xmlattributes { - switch strings.ToUpper(a.Name.Local) { - case VERSION: - case SERVICE: - case STARTINDEX: - case COUNT: - case OUTPUTFORMAT: - default: - n = append(n, a) - } - } - gf.BaseRequest.Attr = ows.StripDuplicateAttr(n) - return nil -} - -// ParseKVP builds a GetCapabilities object based on the available query parameters -// All the keys from the query url.Values need to be UpperCase, this is done during the execution of the operations.ValidRequest() -func (gf *GetFeature) ParseKVP(query url.Values) ows.Exception { - - if len(query) == 0 { - // When there are no query value we know that at least - // the manadorty VERSION parameter is missing. - return ows.MissingParameterValue(VERSION) - } - - q := utils.KeysToUpper(query) - - // Base - if len(q[REQUEST]) > 0 { - gf.XMLName.Local = q[REQUEST][0] - } - - var br BaseRequest - if err := br.ParseKVP(q); err != nil { - return err - } - gf.BaseRequest = br - - // Table 5 - for k, m := range table5 { - if len(q[k]) > 0 { - switch k { - case STARTINDEX: - i, _ := strconv.Atoi(q[k][0]) - gf.Startindex = &i - case COUNT: - i, _ := strconv.Atoi(q[k][0]) - gf.Count = &i - case OUTPUTFORMAT: - gf.OutputFormat = &q[k][0] - case RESULTTYPE: - gf.ResultType = &q[k][0] - default: - if m { - //TODO add return error, missing mandatory key... or accept for now and check during validation - } - } - } - } - - // Table 7 - for k, m := range table7 { - if len(q[k]) > 0 { - switch k { - case NAMESPACES: - gf.BaseRequest.Attr = procesNamespaces(q[k][0]) - default: - if m { - //TODO add return error, missing mandatory key... or accept for now and check during validation - } - } - } - } - - // Table 8 - for k, m := range table8 { - if len(q[k]) > 0 { - switch k { - case TYPENAMES: - gf.Query.TypeNames = q[k][0] - case ALIASES: - // TODO - // 7.9.2.4.3 aliases parameter - // fes:AbstractAdhocQueryExpressionType type (see ISO 19143, 6.3.2) - case SRSNAME: - gf.Query.SrsName = &q[k][0] - case FILTER: - var filter Filter - if err := xml.Unmarshal([]byte(q[k][0]), &filter); err != nil { - // TODO what if the filter is corrupt - // Now it won't unmarshal resulting in a empty/corrupt (but maybe valid) filter object - // Validation of the content is handled further downstream - } - if gf.Query.Filter != nil { - // We are at this point only interressed in the RESOURCEID's - // When none are found the filter will we overwritten with this one from the FILTER= - if gf.Query.Filter.ResourceID != nil { - mergedRids := mergeResourceIDGroups(*gf.Query.Filter.ResourceID, *filter.ResourceID) - filter.ResourceID = &mergedRids - } - } - gf.Query.Filter = &filter - case FILTERLANGUAGE: - // TODO - // See ISO 19143:2010, 6.3.3 seems to behind a pay wall... - // For now we are gonna skip it - case RESOURCEID: - // Resourceid's are - ids := strings.Split(q[k][0], `,`) - var resourceids []ResourceID - for _, id := range ids { - resourceids = append(resourceids, ResourceID{Rid: id}) - } - if gf.Query.Filter != nil { - mergedRids := mergeResourceIDGroups(*gf.Query.Filter.ResourceID, resourceids) - gf.Query.Filter.ResourceID = &mergedRids - } else { - var filter Filter - filter.ResourceID = &resourceids - gf.Query.Filter = &filter - } - case BBOX: - var geobbox GEOBBOX - geobbox.UnmarshalText(q[k][0]) - if gf.Query.Filter != nil { - gf.Query.Filter.BBOX = &geobbox - } else { - var filter Filter - filter.BBOX = &geobbox - gf.Query.Filter = &filter - } - case SORTBY: - default: - if m { - //TODO add return error, missing mandatory key... or accept for now and check during validation - } - } - } - } - return nil -} - -// BuildXML builds a 'new' XML document 'based' on the 'original' XML document -// TODO: In the Filter>Query>... the content of the GeometryOperand (Point,Line,Polygon,...) is the raw xml (text) -func (gf *GetFeature) BuildXML() []byte { - si, _ := xml.MarshalIndent(gf, "", " ") - return append([]byte(xml.Header), si...) -} - -// BuildKVP builds a new query string that will be proxied -func (gf *GetFeature) BuildKVP() url.Values { - querystring := make(map[string][]string) - // base - querystring[REQUEST] = []string{gf.XMLName.Local} - querystring[SERVICE] = []string{gf.BaseRequest.Service} - querystring[VERSION] = []string{gf.BaseRequest.Version} - - // Table 5 - for k, v := range gf.BaseGetFeatureRequest.BuildQueryString() { - querystring[k] = v - } - - // Table 7 - // Table 8 - for k, v := range gf.Query.BuildQueryString() { - querystring[k] = v - } - return querystring -} - -func mergeResourceIDGroups(rids ...[]ResourceID) []ResourceID { - var mergedRids []ResourceID - for _, grp := range rids { - for _, rid := range grp { - mergedRids = append(mergedRids, rid) - } - } - return mergedRids -} - -// the use of a map make that with dublicate namespaces prefixes the last match is used -func procesNamespaces(namespace string) []xml.Attr { - regex := regexp.MustCompile(`xmlns\((.*?)\)`) - namespacematches := regex.FindAllStringSubmatch(namespace, -1) - attributemap := make(map[string]string) - for _, match := range namespacematches { - n := strings.Split(match[1], ",")[0] - v := strings.Split(match[1], ",")[1] - attributemap[n] = v - } - - var attributes []xml.Attr - for k, v := range attributemap { - attributes = append(attributes, xml.Attr{Name: xml.Name{Local: k}, Value: v}) - } - - return attributes -} - -// BaseGetFeatureRequest struct used by GetFeature -type BaseGetFeatureRequest struct { - OutputFormat *string `xml:"outputFormat,attr" yaml:"outputformat"` - Count *int `xml:"count,attr" yaml:"count"` - Startindex *int `xml:"startindex,attr" yaml:"startindex"` - ResultType *string `xml:"resultType,attr" yaml:"resulttype"` -} - -// BuildQueryString for BaseGetFeatureRequest struct -func (b *BaseGetFeatureRequest) BuildQueryString() url.Values { - querystring := make(map[string][]string) - - for k := range table5 { - switch k { - case STARTINDEX: - if b.Startindex != nil { - querystring[STARTINDEX] = []string{strconv.Itoa(*b.Startindex)} - } - case COUNT: - if b.Count != nil { - querystring[COUNT] = []string{strconv.Itoa(*b.Count)} - } - case OUTPUTFORMAT: - if b.OutputFormat != nil { - querystring[OUTPUTFORMAT] = []string{*b.OutputFormat} - } - case RESULTTYPE: - if b.ResultType != nil { - querystring[RESULTTYPE] = []string{*b.ResultType} - } - } - } - return querystring -} - -// BuildQueryString for Query struct -func (q *Query) BuildQueryString() url.Values { - querystring := make(map[string][]string) - - for k := range table8 { - switch k { - case TYPENAMES: - // TODO - // typenames is now a string -> []string - if len(q.TypeNames) > 0 { - querystring[TYPENAMES] = []string{q.TypeNames} - } - case ALIASES: - // TODO - // 7.9.2.4.3 aliases parameter - // fes:AbstractAdhocQueryExpressionType type (see ISO 19143, 6.3.2) - case SRSNAME: - if q.SrsName != nil { - querystring[TYPENAMES] = []string{*q.SrsName} - } - case FILTER: - if q.Filter != nil { - for k, v := range q.Filter.BuildQueryString() { - querystring[k] = v - } - } - case FILTERLANGUAGE: - // TODO - // See ISO 19143:2010, 6.3.3 seems to behind a pay wall... - // For now we are gonna skip it - case RESOURCEID: - // Will be in Filter object - case BBOX: - // Will be in Filter object - case SORTBY: - } - } - return querystring -} - -// Query struct for parsing the WFS filter xml -type Query struct { - TypeNames string `xml:"typeNames,attr" yaml:"typenames"` - SrsName *string `xml:"srsName,attr" yaml:"srsname"` - Filter *Filter `xml:"Filter" yaml:"filter"` - SortBy *SortBy `xml:"SortBy" yaml:"sortby"` - PropertyName *[]string `xml:"PropertyName" yaml:"propertyname"` -} - -// BuildQueryString for Filter struct -func (f *Filter) BuildQueryString() url.Values { - querystring := make(map[string][]string) - si, _ := xml.Marshal(f) - if len(si) > 0 { - querystring[FILTER] = []string{url.QueryEscape(string(si))} - } - return querystring -} - -// Filter struct for Query -type Filter struct { - AND *AND `xml:"AND" yaml:"and"` - OR *OR `xml:"OR" yaml:"or"` - NOT *NOT `xml:"NOT" yaml:"not"` - ResourceID *[]ResourceID `xml:"ResourceId" yaml:"resourceid"` - ComparisonOperator - SpatialOperator -} - -// AND struct for Filter -type AND struct { - AND *AND `xml:"AND" yaml:"and"` - OR *OR `xml:"OR" yaml:"or"` - NOT *NOT `xml:"NOT" yaml:"not"` - ComparisonOperator - SpatialOperator -} - -// OR struct for Filter -type OR struct { - AND *AND `xml:"AND" yaml:"and"` - OR *OR `xml:"OR" yaml:"or"` - NOT *NOT `xml:"NOT" yaml:"not"` - ComparisonOperator - SpatialOperator -} - -// NOT struct for Filter -type NOT struct { - AND *AND `xml:"AND" yaml:"and"` - OR *OR `xml:"OR" yaml:"or"` - NOT *NOT `xml:"NOT" yaml:"not"` - ComparisonOperator - SpatialOperator -} - -// ResourceID struct for Filter -type ResourceID struct { - Rid string `xml:"rid,attr" yaml:"rid"` -} - -// ComparisonOperator struct for Filter -type ComparisonOperator struct { - PropertyIsEqualTo *[]PropertyIsEqualTo `xml:"PropertyIsEqualTo" yaml:"propertyisequalto"` - PropertyIsNotEqualTo *[]PropertyIsNotEqualTo `xml:"PropertyIsNotEqualTo" yaml:"propertyisnotequalto"` - PropertyIsLessThan *[]PropertyIsLessThan `xml:"PropertyIsLessThan" yaml:"propertyislessthan"` - PropertyIsGreaterThan *[]PropertyIsGreaterThan `xml:"PropertyIsGreaterThan" yaml:"propertyisgreaterthan"` - PropertyIsLessThanOrEqualTo *[]PropertyIsLessThanOrEqualTo `xml:"PropertyIsLessThanOrEqualTo" yaml:"propertyislessthanorequalto"` - PropertyIsGreaterThanOrEqualTo *[]PropertyIsGreaterThanOrEqualTo `xml:"PropertyIsGreaterThanOrEqualTo" yaml:"propertyisgreaterthanorequalto"` - PropertyIsBetween *[]PropertyIsBetween `xml:"PropertyIsBetween" yaml:"propertyisbetween"` - PropertyIsLike *[]PropertyIsLike `xml:"PropertyIsLike" yaml:"propertyislike"` -} - -// ComparisonOperatorAttribute struct for the ComparisonOperators -type ComparisonOperatorAttribute struct { - MatchCase *string `xml:"matchCase,attr" yaml:"matchcase"` - PropertyName *string `xml:"PropertyName" yaml:"propertyname"` // property i.e: id - ValueReference *string `xml:"ValueReference" yaml:"valuereference"` // path to a property i.e: the/path/to/a/id/in/a/document or just a id ... - Literal string `xml:"Literal" yaml:"literal"` -} - -// PropertyIsEqualTo for ComparisonOperator -type PropertyIsEqualTo struct { - ComparisonOperatorAttribute -} - -// PropertyIsNotEqualTo for ComparisonOperator -type PropertyIsNotEqualTo struct { - ComparisonOperatorAttribute -} - -// PropertyIsLessThan for ComparisonOperator -type PropertyIsLessThan struct { - ComparisonOperatorAttribute -} - -// PropertyIsGreaterThan for ComparisonOperator -type PropertyIsGreaterThan struct { - ComparisonOperatorAttribute -} - -// PropertyIsLessThanOrEqualTo for ComparisonOperator -type PropertyIsLessThanOrEqualTo struct { - ComparisonOperatorAttribute -} - -// PropertyIsGreaterThanOrEqualTo for ComparisonOperator -type PropertyIsGreaterThanOrEqualTo struct { - ComparisonOperatorAttribute -} - -// PropertyIsLike for ComparisonOperator -// wildcard='*' singleChar='.' escape='!'> -type PropertyIsLike struct { - Wildcard string `xml:"wildcard,attr" yaml:"wildcard"` - SingleChar string `xml:"singleChar,attr" yaml:"singlechar"` - Escape string `xml:"escape,attr" yaml:"escape"` - ComparisonOperatorAttribute -} - -// PropertyIsBetween for ComparisonOperator -type PropertyIsBetween struct { - PropertyName string `xml:"PropertyName" yaml:"propertyname"` - LowerBoundary string `xml:"LowerBoundary" yaml:"lowerboundary"` - UpperBoundary string `xml:"UpperBoundary" yaml:"upperboundary"` -} - -// GeometryOperand struct for Filter -type GeometryOperand struct { - Point *Point `xml:"Point" yaml:"point"` - MultiPoint *MultiPoint `xml:"MultiPoint" yaml:"multipoint"` - LineString *LineString `xml:"LineString" yaml:"linestring"` - MultiLineString *MultiLineString `xml:"MultiLineString" yaml:"multiLinestring"` - Curve *Curve `xml:"Curve" yaml:"curve"` - MultiCurve *MultiCurve `xml:"MultiCurve" yaml:"multicurve"` - Polygon *Polygon `xml:"Polygon" yaml:"polygon"` - MultiPolygon *MultiPolygon `xml:"MultiPolygon" yaml:"multipolygon"` - Surface *Surface `xml:"Surface" yaml:"surface"` - MultiSurface *MultiSurface `xml:"MultiSurface" yaml:"multisurface"` - Box *Box `xml:"Box" yaml:"box"` - Envelope *Envelope `xml:"Envelope" yaml:"envelope"` -} - -// Geometry struct for GeometryOperand geometries -type Geometry struct { - SrsName string `xml:"srsName,attr" yaml:"srname"` - Content string `xml:",innerxml"` -} - -// Point struct for GeometryOperand -type Point struct { - Geometry -} - -// MultiPoint struct for GeometryOperand -type MultiPoint struct { - Geometry -} - -// LineString struct for GeometryOperand -type LineString struct { - Geometry -} - -// MultiLineString struct for GeometryOperand -type MultiLineString struct { - Geometry -} - -// Curve struct for GeometryOperand -type Curve struct { - Geometry -} - -// MultiCurve struct for GeometryOperand -type MultiCurve struct { - Geometry -} - -// Polygon struct for GeometryOperand -type Polygon struct { - Geometry -} - -// MultiPolygon struct for GeometryOperand -type MultiPolygon struct { - Geometry -} - -// Surface struct for GeometryOperand -type Surface struct { - Geometry -} - -// MultiSurface struct for GeometryOperand -type MultiSurface struct { - Geometry -} - -// Box struct for GeometryOperand -type Box struct { - Geometry -} - -// Envelope struct for GeometryOperand -type Envelope struct { - LowerCorner ows.Position `xml:"lowerCorner" yaml:"lowercorner"` - UpperCorner ows.Position `xml:"upperCorner" yaml:"uppercorner"` -} - -// SpatialOperator struct for Filter -type SpatialOperator struct { - Equals *Equals `xml:"Equals" yaml:"equals"` - Disjoint *Disjoint `xml:"Disjoint" yaml:"disjoint"` - Touches *Touches `xml:"Touches" yaml:"touches"` - Within *Within `xml:"Within" yaml:"within"` - Overlaps *Overlaps `xml:"Overlaps" yaml:"overlaps"` - Crosses *Crosses `xml:"Crosses" yaml:"crosses"` - Intersects *Intersects `xml:"Intersects" yaml:"intersects"` - Contains *Contains `xml:"Contains" yaml:"contains"` - DWithin *DWithin `xml:"DWithin" yaml:"dwithin"` - Beyond *Beyond `xml:"Beyond" yaml:"beyond"` - BBOX *GEOBBOX `xml:"BBOX" yaml:"bbox"` -} - -// Equals for SpatialOperator -type Equals struct { - PropertyName string `xml:"PropertyName" yaml:"propertyname"` - GeometryOperand -} - -// Disjoint for SpatialOperator -type Disjoint struct { - PropertyName string `xml:"PropertyName" yaml:"propertyname"` - GeometryOperand -} - -// Touches for SpatialOperator -type Touches struct { - PropertyName string `xml:"PropertyName" yaml:"propertyname"` - GeometryOperand -} - -// Within for SpatialOperator -type Within struct { - PropertyName string `xml:"PropertyName" yaml:"propertyname"` - GeometryOperand -} - -// Overlaps for SpatialOperator -type Overlaps struct { - PropertyName string `xml:"PropertyName" yaml:"propertyname"` - GeometryOperand -} - -// Crosses for SpatialOperator -type Crosses struct { - PropertyName string `xml:"PropertyName" yaml:"propertyname"` - GeometryOperand -} - -// Intersects for SpatialOperator -type Intersects struct { - PropertyName string `xml:"PropertyName" yaml:"propertyname"` - GeometryOperand -} - -// Contains for SpatialOperator -type Contains struct { - PropertyName string `xml:"PropertyName" yaml:"propertyname"` - GeometryOperand -} - -// DWithin for SpatialOperator -type DWithin struct { - PropertyName string `xml:"PropertyName" yaml:"propertyname"` - GeometryOperand - Distance Distance `xml:"Distance" yaml:"distance"` -} - -// Beyond for SpatialOperator -type Beyond struct { - Units string `xml:"unit,attr" yaml:"unit"` - GeometryOperand - Distance Distance `xml:"Distance" yaml:"distance"` -} - -// Distance for DWithin and Beyond -type Distance struct { - Units string `xml:"units,attr" yaml:"unit"` - Text string `xml:",chardata"` -} - -// GEOBBOX for SpatialOperator -type GEOBBOX struct { - Units *string `xml:"unit,attr" yaml:"unit"` // unit or units.. - SrsName *string `xml:"srsName,attr" yaml:"srsname"` - ValueReference *string `xml:"ValueReference" yaml:"valuereference"` - Envelope Envelope `xml:"Envelope" yaml:"envelope"` - // Text string `xml:",chardata"` - // - // /RS1/geometry - // - // 10 10 - // 20 20 - // - // -} - -// UnmarshalText a string to a GEOBBOX object -func (gb *GEOBBOX) UnmarshalText(q string) ows.Exception { - regex := regexp.MustCompile(`,`) - result := regex.Split(q, -1) - if len(result) == 4 || len(result) == 5 { - - var lx, ly, ux, uy float64 - var err error - - if lx, err = strconv.ParseFloat(result[0], 64); err != nil { - return exception.InvalidValue(BBOX) - } - if ly, err = strconv.ParseFloat(result[1], 64); err != nil { - return exception.InvalidValue(BBOX) - } - if ux, err = strconv.ParseFloat(result[2], 64); err != nil { - return exception.InvalidValue(BBOX) - } - if uy, err = strconv.ParseFloat(result[3], 64); err != nil { - return exception.InvalidValue(BBOX) - } - - gb.Envelope.LowerCorner = ows.Position{lx, ly} - gb.Envelope.UpperCorner = ows.Position{ux, uy} - } - if len(result) == 5 { - gb.SrsName = &result[4] - } - return nil -} - -// MarshalText build a KVP string of a GEOBBOX object -func (gb *GEOBBOX) MarshalText() string { - regex := regexp.MustCompile(` `) - var str string - if len(gb.Envelope.LowerCorner) >= 2 && len(gb.Envelope.UpperCorner) >= 2 && gb.Envelope.LowerCorner != gb.Envelope.UpperCorner { - str = fmt.Sprintf("%f,%f,%f,%f", gb.Envelope.LowerCorner[0], gb.Envelope.LowerCorner[1], gb.Envelope.UpperCorner[0], gb.Envelope.UpperCorner[1]) - } - if len(str) > 0 && gb.SrsName != nil { - str = str + ` ` + *gb.SrsName - } - return regex.ReplaceAllString(str, `,`) -} - -// SortBy for Query -type SortBy struct { - SortProperty *[]SortProperty `xml:"SortProperty" yaml:"sortproperty"` -} - -// SortProperty for SortBy -type SortProperty struct { - Content string `xml:",innerxml"` -} - -// ProjectionClause based on Table 9 WFS2.0.0 spec -type ProjectionClause struct { - Propertyname string -} - -// StoredQuery based on Table 10 WFS2.0.0 spec -type StoredQuery struct { - StoredQueryID string -} - -// GetFeature struct with the needed parameters/attributes needed for making a GetFeature request -type GetFeature struct { - XMLName xml.Name `xml:"GetFeature" yaml:"getfeature"` - BaseRequest - BaseGetFeatureRequest - Query Query `xml:"Query" yaml:"query"` -} +package request + +import ( + "encoding/xml" + "fmt" + "net/url" + "regexp" + "strconv" + "strings" + + "github.com/pdok/ogc-specifications/pkg/ows" + "github.com/pdok/ogc-specifications/pkg/utils" + "github.com/pdok/ogc-specifications/pkg/wfs200/exception" +) + +// Contains the GetFeature struct and specific functions for building a GetFeature request + +// +const ( + getfeature = `GetFeature` + + // table5 + STARTINDEX = `STARTINDEX` + COUNT = `COUNT` + RESULTTYPE = `RESULTTYPE` + // table6 + RESOLVE = `RESOLVE` + RESOLVEDEPTH = `RESOLVEDEPTH` + RESOLVETIMEOUT = `RESOLVETIMEOUT` + // table7 + NAMESPACES = `NAMESPACES` + // table8 + TYPENAMES = `TYPENAMES` + ALIASES = `ALIASES` + SRSNAME = `SRSNAME` + FILTER = `FILTER` + FILTERLANGUAGE = `FILTER_LANGUAGE` + RESOURCEID = `RESOURCEID` + BBOX = `BBOX` // OGC 06-121r3 + SORTBY = `SORTBY` + // table10 + STOREDQUERYID = `STOREDQUERY_ID` +) + +// Type returns GetFeature +func (gf *GetFeature) Type() string { + return getfeature +} + +// Validate returns GetFeature +func (gf *GetFeature) Validate() ows.Exception { + return nil +} + +// WFS tables as map[string]bool, where the key (string) is the TOKEN and the bool if its a mandatory (true) or optional (false) attribute +var table5 = map[string]bool{STARTINDEX: false, COUNT: false, OUTPUTFORMAT: false, RESULTTYPE: false} + +//var table6 = map[string]bool{RESOLVE: false, RESOLVEDEPTH: false, RESOLVETIMEOUT: false} +var table7 = map[string]bool{NAMESPACES: false} //VSPs (<- vendor specific parameters) +var table8 = map[string]bool{TYPENAMES: true, ALIASES: false, SRSNAME: false, FILTER: false, FILTERLANGUAGE: false, RESOURCEID: false, BBOX: false, SORTBY: false} + +//var table10 = map[string]bool{STOREDQUERYID: true} //storedquery_parameter=value + +// ParseXML builds a GetCapabilities object based on a XML document +func (gf *GetFeature) ParseXML(doc []byte) ows.Exception { + var xmlattributes ows.XMLAttribute + if err := xml.Unmarshal(doc, &xmlattributes); err != nil { + return ows.NoApplicableCode("Could not process XML, is it XML?") + } + xml.Unmarshal(doc, &gf) //When object can be Unmarshalled -> XMLAttributes, it can be Unmarshalled -> GetFeature + var n []xml.Attr + for _, a := range xmlattributes { + switch strings.ToUpper(a.Name.Local) { + case VERSION: + case SERVICE: + case STARTINDEX: + case COUNT: + case OUTPUTFORMAT: + default: + n = append(n, a) + } + } + gf.BaseRequest.Attr = ows.StripDuplicateAttr(n) + return nil +} + +// ParseKVP builds a GetCapabilities object based on the available query parameters +// All the keys from the query url.Values need to be UpperCase, this is done during the execution of the operations.ValidRequest() +func (gf *GetFeature) ParseKVP(query url.Values) ows.Exception { + + if len(query) == 0 { + // When there are no query value we know that at least + // the manadorty VERSION parameter is missing. + return ows.MissingParameterValue(VERSION) + } + + q := utils.KeysToUpper(query) + + // Base + if len(q[REQUEST]) > 0 { + gf.XMLName.Local = q[REQUEST][0] + } + + var br BaseRequest + if err := br.ParseKVP(q); err != nil { + return err + } + gf.BaseRequest = br + + // Table 5 + for k, m := range table5 { + if len(q[k]) > 0 { + switch k { + case STARTINDEX: + i, _ := strconv.Atoi(q[k][0]) + gf.Startindex = &i + case COUNT: + i, _ := strconv.Atoi(q[k][0]) + gf.Count = &i + case OUTPUTFORMAT: + gf.OutputFormat = &q[k][0] + case RESULTTYPE: + gf.ResultType = &q[k][0] + default: + if m { + //TODO add return error, missing mandatory key... or accept for now and check during validation + } + } + } + } + + // Table 7 + for k, m := range table7 { + if len(q[k]) > 0 { + switch k { + case NAMESPACES: + gf.BaseRequest.Attr = procesNamespaces(q[k][0]) + default: + if m { + //TODO add return error, missing mandatory key... or accept for now and check during validation + } + } + } + } + + // Table 8 + for k, m := range table8 { + if len(q[k]) > 0 { + switch k { + case TYPENAMES: + gf.Query.TypeNames = q[k][0] + case ALIASES: + // TODO + // 7.9.2.4.3 aliases parameter + // fes:AbstractAdhocQueryExpressionType type (see ISO 19143, 6.3.2) + case SRSNAME: + gf.Query.SrsName = &q[k][0] + case FILTER: + var filter Filter + if err := xml.Unmarshal([]byte(q[k][0]), &filter); err != nil { + // TODO what if the filter is corrupt + // Now it won't unmarshal resulting in a empty/corrupt (but maybe valid) filter object + // Validation of the content is handled further downstream + } + if gf.Query.Filter != nil { + // We are at this point only interressed in the RESOURCEID's + // When none are found the filter will we overwritten with this one from the FILTER= + if gf.Query.Filter.ResourceID != nil { + mergedRids := mergeResourceIDGroups(*gf.Query.Filter.ResourceID, *filter.ResourceID) + filter.ResourceID = &mergedRids + } + } + gf.Query.Filter = &filter + case FILTERLANGUAGE: + // TODO + // See ISO 19143:2010, 6.3.3 seems to behind a pay wall... + // For now we are gonna skip it + case RESOURCEID: + // Resourceid's are + ids := strings.Split(q[k][0], `,`) + var resourceids []ResourceID + for _, id := range ids { + resourceids = append(resourceids, ResourceID{Rid: id}) + } + if gf.Query.Filter != nil { + mergedRids := mergeResourceIDGroups(*gf.Query.Filter.ResourceID, resourceids) + gf.Query.Filter.ResourceID = &mergedRids + } else { + var filter Filter + filter.ResourceID = &resourceids + gf.Query.Filter = &filter + } + case BBOX: + var geobbox GEOBBOX + geobbox.UnmarshalText(q[k][0]) + if gf.Query.Filter != nil { + gf.Query.Filter.BBOX = &geobbox + } else { + var filter Filter + filter.BBOX = &geobbox + gf.Query.Filter = &filter + } + case SORTBY: + default: + if m { + //TODO add return error, missing mandatory key... or accept for now and check during validation + } + } + } + } + return nil +} + +// BuildXML builds a 'new' XML document 'based' on the 'original' XML document +// TODO: In the Filter>Query>... the content of the GeometryOperand (Point,Line,Polygon,...) is the raw xml (text) +func (gf *GetFeature) BuildXML() []byte { + si, _ := xml.MarshalIndent(gf, "", " ") + return append([]byte(xml.Header), si...) +} + +// BuildKVP builds a new query string that will be proxied +func (gf *GetFeature) BuildKVP() url.Values { + querystring := make(map[string][]string) + // base + querystring[REQUEST] = []string{gf.XMLName.Local} + querystring[SERVICE] = []string{gf.BaseRequest.Service} + querystring[VERSION] = []string{gf.BaseRequest.Version} + + // Table 5 + for k, v := range gf.BaseGetFeatureRequest.BuildQueryString() { + querystring[k] = v + } + + // Table 7 + // Table 8 + for k, v := range gf.Query.BuildQueryString() { + querystring[k] = v + } + return querystring +} + +func mergeResourceIDGroups(rids ...[]ResourceID) []ResourceID { + var mergedRids []ResourceID + for _, grp := range rids { + for _, rid := range grp { + mergedRids = append(mergedRids, rid) + } + } + return mergedRids +} + +// the use of a map make that with dublicate namespaces prefixes the last match is used +func procesNamespaces(namespace string) []xml.Attr { + regex := regexp.MustCompile(`xmlns\((.*?)\)`) + namespacematches := regex.FindAllStringSubmatch(namespace, -1) + attributemap := make(map[string]string) + for _, match := range namespacematches { + n := strings.Split(match[1], ",")[0] + v := strings.Split(match[1], ",")[1] + attributemap[n] = v + } + + var attributes []xml.Attr + for k, v := range attributemap { + attributes = append(attributes, xml.Attr{Name: xml.Name{Local: k}, Value: v}) + } + + return attributes +} + +// BaseGetFeatureRequest struct used by GetFeature +type BaseGetFeatureRequest struct { + OutputFormat *string `xml:"outputFormat,attr" yaml:"outputformat"` + Count *int `xml:"count,attr" yaml:"count"` + Startindex *int `xml:"startindex,attr" yaml:"startindex"` + ResultType *string `xml:"resultType,attr" yaml:"resulttype"` +} + +// BuildQueryString for BaseGetFeatureRequest struct +func (b *BaseGetFeatureRequest) BuildQueryString() url.Values { + querystring := make(map[string][]string) + + for k := range table5 { + switch k { + case STARTINDEX: + if b.Startindex != nil { + querystring[STARTINDEX] = []string{strconv.Itoa(*b.Startindex)} + } + case COUNT: + if b.Count != nil { + querystring[COUNT] = []string{strconv.Itoa(*b.Count)} + } + case OUTPUTFORMAT: + if b.OutputFormat != nil { + querystring[OUTPUTFORMAT] = []string{*b.OutputFormat} + } + case RESULTTYPE: + if b.ResultType != nil { + querystring[RESULTTYPE] = []string{*b.ResultType} + } + } + } + return querystring +} + +// BuildQueryString for Query struct +func (q *Query) BuildQueryString() url.Values { + querystring := make(map[string][]string) + + for k := range table8 { + switch k { + case TYPENAMES: + // TODO + // typenames is now a string -> []string + if len(q.TypeNames) > 0 { + querystring[TYPENAMES] = []string{q.TypeNames} + } + case ALIASES: + // TODO + // 7.9.2.4.3 aliases parameter + // fes:AbstractAdhocQueryExpressionType type (see ISO 19143, 6.3.2) + case SRSNAME: + if q.SrsName != nil { + querystring[TYPENAMES] = []string{*q.SrsName} + } + case FILTER: + if q.Filter != nil { + for k, v := range q.Filter.BuildQueryString() { + querystring[k] = v + } + } + case FILTERLANGUAGE: + // TODO + // See ISO 19143:2010, 6.3.3 seems to behind a pay wall... + // For now we are gonna skip it + case RESOURCEID: + // Will be in Filter object + case BBOX: + // Will be in Filter object + case SORTBY: + } + } + return querystring +} + +// Query struct for parsing the WFS filter xml +type Query struct { + TypeNames string `xml:"typeNames,attr" yaml:"typenames"` + SrsName *string `xml:"srsName,attr" yaml:"srsname"` + Filter *Filter `xml:"Filter" yaml:"filter"` + SortBy *SortBy `xml:"SortBy" yaml:"sortby"` + PropertyName *[]string `xml:"PropertyName" yaml:"propertyname"` +} + +// BuildQueryString for Filter struct +func (f *Filter) BuildQueryString() url.Values { + querystring := make(map[string][]string) + si, _ := xml.Marshal(f) + if len(si) > 0 { + querystring[FILTER] = []string{url.QueryEscape(string(si))} + } + return querystring +} + +// Filter struct for Query +type Filter struct { + AND *AND `xml:"AND" yaml:"and"` + OR *OR `xml:"OR" yaml:"or"` + NOT *NOT `xml:"NOT" yaml:"not"` + ResourceID *[]ResourceID `xml:"ResourceId" yaml:"resourceid"` + ComparisonOperator + SpatialOperator +} + +// AND struct for Filter +type AND struct { + AND *AND `xml:"AND" yaml:"and"` + OR *OR `xml:"OR" yaml:"or"` + NOT *NOT `xml:"NOT" yaml:"not"` + ComparisonOperator + SpatialOperator +} + +// OR struct for Filter +type OR struct { + AND *AND `xml:"AND" yaml:"and"` + OR *OR `xml:"OR" yaml:"or"` + NOT *NOT `xml:"NOT" yaml:"not"` + ComparisonOperator + SpatialOperator +} + +// NOT struct for Filter +type NOT struct { + AND *AND `xml:"AND" yaml:"and"` + OR *OR `xml:"OR" yaml:"or"` + NOT *NOT `xml:"NOT" yaml:"not"` + ComparisonOperator + SpatialOperator +} + +// ResourceID struct for Filter +type ResourceID struct { + Rid string `xml:"rid,attr" yaml:"rid"` +} + +// ComparisonOperator struct for Filter +type ComparisonOperator struct { + PropertyIsEqualTo *[]PropertyIsEqualTo `xml:"PropertyIsEqualTo" yaml:"propertyisequalto"` + PropertyIsNotEqualTo *[]PropertyIsNotEqualTo `xml:"PropertyIsNotEqualTo" yaml:"propertyisnotequalto"` + PropertyIsLessThan *[]PropertyIsLessThan `xml:"PropertyIsLessThan" yaml:"propertyislessthan"` + PropertyIsGreaterThan *[]PropertyIsGreaterThan `xml:"PropertyIsGreaterThan" yaml:"propertyisgreaterthan"` + PropertyIsLessThanOrEqualTo *[]PropertyIsLessThanOrEqualTo `xml:"PropertyIsLessThanOrEqualTo" yaml:"propertyislessthanorequalto"` + PropertyIsGreaterThanOrEqualTo *[]PropertyIsGreaterThanOrEqualTo `xml:"PropertyIsGreaterThanOrEqualTo" yaml:"propertyisgreaterthanorequalto"` + PropertyIsBetween *[]PropertyIsBetween `xml:"PropertyIsBetween" yaml:"propertyisbetween"` + PropertyIsLike *[]PropertyIsLike `xml:"PropertyIsLike" yaml:"propertyislike"` +} + +// ComparisonOperatorAttribute struct for the ComparisonOperators +type ComparisonOperatorAttribute struct { + MatchCase *string `xml:"matchCase,attr" yaml:"matchcase"` + PropertyName *string `xml:"PropertyName" yaml:"propertyname"` // property i.e: id + ValueReference *string `xml:"ValueReference" yaml:"valuereference"` // path to a property i.e: the/path/to/a/id/in/a/document or just a id ... + Literal string `xml:"Literal" yaml:"literal"` +} + +// PropertyIsEqualTo for ComparisonOperator +type PropertyIsEqualTo struct { + ComparisonOperatorAttribute +} + +// PropertyIsNotEqualTo for ComparisonOperator +type PropertyIsNotEqualTo struct { + ComparisonOperatorAttribute +} + +// PropertyIsLessThan for ComparisonOperator +type PropertyIsLessThan struct { + ComparisonOperatorAttribute +} + +// PropertyIsGreaterThan for ComparisonOperator +type PropertyIsGreaterThan struct { + ComparisonOperatorAttribute +} + +// PropertyIsLessThanOrEqualTo for ComparisonOperator +type PropertyIsLessThanOrEqualTo struct { + ComparisonOperatorAttribute +} + +// PropertyIsGreaterThanOrEqualTo for ComparisonOperator +type PropertyIsGreaterThanOrEqualTo struct { + ComparisonOperatorAttribute +} + +// PropertyIsLike for ComparisonOperator +// wildcard='*' singleChar='.' escape='!'> +type PropertyIsLike struct { + Wildcard string `xml:"wildcard,attr" yaml:"wildcard"` + SingleChar string `xml:"singleChar,attr" yaml:"singlechar"` + Escape string `xml:"escape,attr" yaml:"escape"` + ComparisonOperatorAttribute +} + +// PropertyIsBetween for ComparisonOperator +type PropertyIsBetween struct { + PropertyName string `xml:"PropertyName" yaml:"propertyname"` + LowerBoundary string `xml:"LowerBoundary" yaml:"lowerboundary"` + UpperBoundary string `xml:"UpperBoundary" yaml:"upperboundary"` +} + +// GeometryOperand struct for Filter +type GeometryOperand struct { + Point *Point `xml:"Point" yaml:"point"` + MultiPoint *MultiPoint `xml:"MultiPoint" yaml:"multipoint"` + LineString *LineString `xml:"LineString" yaml:"linestring"` + MultiLineString *MultiLineString `xml:"MultiLineString" yaml:"multiLinestring"` + Curve *Curve `xml:"Curve" yaml:"curve"` + MultiCurve *MultiCurve `xml:"MultiCurve" yaml:"multicurve"` + Polygon *Polygon `xml:"Polygon" yaml:"polygon"` + MultiPolygon *MultiPolygon `xml:"MultiPolygon" yaml:"multipolygon"` + Surface *Surface `xml:"Surface" yaml:"surface"` + MultiSurface *MultiSurface `xml:"MultiSurface" yaml:"multisurface"` + Box *Box `xml:"Box" yaml:"box"` + Envelope *Envelope `xml:"Envelope" yaml:"envelope"` +} + +// Geometry struct for GeometryOperand geometries +type Geometry struct { + SrsName string `xml:"srsName,attr" yaml:"srname"` + Content string `xml:",innerxml"` +} + +// Point struct for GeometryOperand +type Point struct { + Geometry +} + +// MultiPoint struct for GeometryOperand +type MultiPoint struct { + Geometry +} + +// LineString struct for GeometryOperand +type LineString struct { + Geometry +} + +// MultiLineString struct for GeometryOperand +type MultiLineString struct { + Geometry +} + +// Curve struct for GeometryOperand +type Curve struct { + Geometry +} + +// MultiCurve struct for GeometryOperand +type MultiCurve struct { + Geometry +} + +// Polygon struct for GeometryOperand +type Polygon struct { + Geometry +} + +// MultiPolygon struct for GeometryOperand +type MultiPolygon struct { + Geometry +} + +// Surface struct for GeometryOperand +type Surface struct { + Geometry +} + +// MultiSurface struct for GeometryOperand +type MultiSurface struct { + Geometry +} + +// Box struct for GeometryOperand +type Box struct { + Geometry +} + +// Envelope struct for GeometryOperand +type Envelope struct { + LowerCorner ows.Position `xml:"lowerCorner" yaml:"lowercorner"` + UpperCorner ows.Position `xml:"upperCorner" yaml:"uppercorner"` +} + +// SpatialOperator struct for Filter +type SpatialOperator struct { + Equals *Equals `xml:"Equals" yaml:"equals"` + Disjoint *Disjoint `xml:"Disjoint" yaml:"disjoint"` + Touches *Touches `xml:"Touches" yaml:"touches"` + Within *Within `xml:"Within" yaml:"within"` + Overlaps *Overlaps `xml:"Overlaps" yaml:"overlaps"` + Crosses *Crosses `xml:"Crosses" yaml:"crosses"` + Intersects *Intersects `xml:"Intersects" yaml:"intersects"` + Contains *Contains `xml:"Contains" yaml:"contains"` + DWithin *DWithin `xml:"DWithin" yaml:"dwithin"` + Beyond *Beyond `xml:"Beyond" yaml:"beyond"` + BBOX *GEOBBOX `xml:"BBOX" yaml:"bbox"` +} + +// Equals for SpatialOperator +type Equals struct { + PropertyName string `xml:"PropertyName" yaml:"propertyname"` + GeometryOperand +} + +// Disjoint for SpatialOperator +type Disjoint struct { + PropertyName string `xml:"PropertyName" yaml:"propertyname"` + GeometryOperand +} + +// Touches for SpatialOperator +type Touches struct { + PropertyName string `xml:"PropertyName" yaml:"propertyname"` + GeometryOperand +} + +// Within for SpatialOperator +type Within struct { + PropertyName string `xml:"PropertyName" yaml:"propertyname"` + GeometryOperand +} + +// Overlaps for SpatialOperator +type Overlaps struct { + PropertyName string `xml:"PropertyName" yaml:"propertyname"` + GeometryOperand +} + +// Crosses for SpatialOperator +type Crosses struct { + PropertyName string `xml:"PropertyName" yaml:"propertyname"` + GeometryOperand +} + +// Intersects for SpatialOperator +type Intersects struct { + PropertyName string `xml:"PropertyName" yaml:"propertyname"` + GeometryOperand +} + +// Contains for SpatialOperator +type Contains struct { + PropertyName string `xml:"PropertyName" yaml:"propertyname"` + GeometryOperand +} + +// DWithin for SpatialOperator +type DWithin struct { + PropertyName string `xml:"PropertyName" yaml:"propertyname"` + GeometryOperand + Distance Distance `xml:"Distance" yaml:"distance"` +} + +// Beyond for SpatialOperator +type Beyond struct { + Units string `xml:"unit,attr" yaml:"unit"` + GeometryOperand + Distance Distance `xml:"Distance" yaml:"distance"` +} + +// Distance for DWithin and Beyond +type Distance struct { + Units string `xml:"units,attr" yaml:"unit"` + Text string `xml:",chardata"` +} + +// GEOBBOX for SpatialOperator +type GEOBBOX struct { + Units *string `xml:"unit,attr" yaml:"unit"` // unit or units.. + SrsName *string `xml:"srsName,attr" yaml:"srsname"` + ValueReference *string `xml:"ValueReference" yaml:"valuereference"` + Envelope Envelope `xml:"Envelope" yaml:"envelope"` + // Text string `xml:",chardata"` + // + // /RS1/geometry + // + // 10 10 + // 20 20 + // + // +} + +// UnmarshalText a string to a GEOBBOX object +func (gb *GEOBBOX) UnmarshalText(q string) ows.Exception { + regex := regexp.MustCompile(`,`) + result := regex.Split(q, -1) + if len(result) == 4 || len(result) == 5 { + + var lx, ly, ux, uy float64 + var err error + + if lx, err = strconv.ParseFloat(result[0], 64); err != nil { + return exception.InvalidValue(BBOX) + } + if ly, err = strconv.ParseFloat(result[1], 64); err != nil { + return exception.InvalidValue(BBOX) + } + if ux, err = strconv.ParseFloat(result[2], 64); err != nil { + return exception.InvalidValue(BBOX) + } + if uy, err = strconv.ParseFloat(result[3], 64); err != nil { + return exception.InvalidValue(BBOX) + } + + gb.Envelope.LowerCorner = ows.Position{lx, ly} + gb.Envelope.UpperCorner = ows.Position{ux, uy} + } + if len(result) == 5 { + gb.SrsName = &result[4] + } + return nil +} + +// MarshalText build a KVP string of a GEOBBOX object +func (gb *GEOBBOX) MarshalText() string { + regex := regexp.MustCompile(` `) + var str string + if len(gb.Envelope.LowerCorner) >= 2 && len(gb.Envelope.UpperCorner) >= 2 && gb.Envelope.LowerCorner != gb.Envelope.UpperCorner { + str = fmt.Sprintf("%f,%f,%f,%f", gb.Envelope.LowerCorner[0], gb.Envelope.LowerCorner[1], gb.Envelope.UpperCorner[0], gb.Envelope.UpperCorner[1]) + } + if len(str) > 0 && gb.SrsName != nil { + str = str + ` ` + *gb.SrsName + } + return regex.ReplaceAllString(str, `,`) +} + +// SortBy for Query +type SortBy struct { + SortProperty *[]SortProperty `xml:"SortProperty" yaml:"sortproperty"` +} + +// SortProperty for SortBy +type SortProperty struct { + Content string `xml:",innerxml"` +} + +// ProjectionClause based on Table 9 WFS2.0.0 spec +type ProjectionClause struct { + Propertyname string +} + +// StoredQuery based on Table 10 WFS2.0.0 spec +type StoredQuery struct { + StoredQueryID string +} + +// GetFeature struct with the needed parameters/attributes needed for making a GetFeature request +type GetFeature struct { + XMLName xml.Name `xml:"GetFeature" yaml:"getfeature"` + BaseRequest + BaseGetFeatureRequest + Query Query `xml:"Query" yaml:"query"` +} diff --git a/pkg/wfs200/response/getcapabilities.go b/pkg/wfs200/response/getcapabilities.go index c459d13..3f542a4 100644 --- a/pkg/wfs200/response/getcapabilities.go +++ b/pkg/wfs200/response/getcapabilities.go @@ -1,126 +1,126 @@ -package response - -import ( - "encoding/xml" - "regexp" - - "github.com/pdok/ogc-specifications/pkg/ows" - "github.com/pdok/ogc-specifications/pkg/wfs200/capabilities" -) - -// -const ( - getcapabilities = `GetCapabilities` -) - -// -const ( - Service = `WFS` - Version = `2.0.0` -) - -// Contains the WFS200 struct - -// Type function needed for the interface -func (gc *GetCapabilities) Type() string { - return getcapabilities -} - -// Service function needed for the interface -func (gc *GetCapabilities) Service() string { - return Service -} - -// Version function needed for the interface -func (gc *GetCapabilities) Version() string { - return Version -} - -// Validate function of the wfs200 spec -func (gc *GetCapabilities) Validate() ows.Exception { - return nil -} - -// BuildXML builds a GetCapabilities response object -func (gc *GetCapabilities) BuildXML() []byte { - si, _ := xml.MarshalIndent(gc, "", "") - re := regexp.MustCompile(`><.*>`) - return []byte(xml.Header + re.ReplaceAllString(string(si), "/>")) -} - -// GetCapabilities base struct -type GetCapabilities struct { - XMLName xml.Name `xml:"wfs:WFS_Capabilities"` - Namespaces `yaml:"namespaces"` - ServiceIdentification ServiceIdentification `xml:"ows:ServiceIdentification" yaml:"serviceidentification"` - ServiceProvider ServiceProvider `xml:"ows:ServiceProvider" yaml:"serviceprovider"` - OperationsMetadata capabilities.OperationsMetadata `xml:"ows:OperationsMetadata" yaml:"operationsmetadata"` - FeatureTypeList capabilities.FeatureTypeList `xml:"wfs:FeatureTypeList" yaml:"featuretypelist"` - FilterCapabilities capabilities.FilterCapabilities `xml:"fes:Filter_Capabilities" yaml:"filtercapabilities"` -} - -// Namespaces struct containing the namespaces needed for the XML document -type Namespaces struct { - XmlnsGML string `xml:"xmlns:gml,attr" yaml:"gml"` //http://www.opengis.net/gml/3.2 - XmlnsWFS string `xml:"xmlns:wfs,attr" yaml:"wfs"` //http://www.opengis.net/wfs/2.0 - XmlnsOWS string `xml:"xmlns:ows,attr" yaml:"ows"` //http://www.opengis.net/ows/1.1 - XmlnsXlink string `xml:"xmlns:xlink,attr" yaml:"xlink"` //http://www.w3.org/1999/xlink - XmlnsXSI string `xml:"xmlns:xsi,attr" yaml:"xsi"` //http://www.w3.org/2001/XMLSchema-instance - XmlnsFes string `xml:"xmlns:fes,attr" yaml:"fes"` //http://www.opengis.net/fes/2.0 - XmlnsInspireCommon string `xml:"xmlns:inspire_common,attr,omitempty" yaml:"inspirecommon,omitempty"` //http://inspire.ec.europa.eu/schemas/common/1.0 - XmlnsInspireDls string `xml:"xmlns:inspire_dls,attr,omitempty" yaml:"inspiredls,omitempty"` //http://inspire.ec.europa.eu/schemas/inspire_dls/1.0 - XmlnsPrefix string `xml:"xmlns:{{.Prefix}},attr" yaml:"prefix"` //namespace_uri placeholder - Version string `xml:"version,attr" yaml:"version"` - SchemaLocation string `xml:"xsi:schemaLocation,attr" yaml:"schemalocation"` -} - -// ServiceIdentification struct should only be fill by the "template" configuration wfs200.yaml -type ServiceIdentification struct { - XMLName xml.Name `xml:"ows:ServiceIdentification"` - Title string `xml:"ows:Title" yaml:"title"` - Abstract string `xml:"ows:Abstract" yaml:"abstract"` - Keywords *ows.Keywords `xml:"ows:Keywords" yaml:"keywords"` - ServiceType struct { - Text string `xml:",chardata" yaml:"text"` - CodeSpace string `xml:"codeSpace,attr" yaml:"codespace"` - } `xml:"ows:ServiceType"` - ServiceTypeVersion string `xml:"ows:ServiceTypeVersion" yaml:"servicetypeversion"` - Fees string `xml:"ows:Fees" yaml:"fees"` - AccessConstraints string `xml:"ows:AccessConstraints" yaml:"accesscontraints"` -} - -// ServiceProvider struct containing the provider/organization information should only be fill by the "template" configuration wfs200.yaml -type ServiceProvider struct { - XMLName xml.Name `xml:"ows:ServiceProvider"` - ProviderName string `xml:"ows:ProviderName" yaml:"providername"` - ProviderSite struct { - Type string `xml:"xlink:type,attr" yaml:"type"` - Href string `xml:"xlink:href,attr" yaml:"href"` - } `xml:"ows:ProviderSite" yaml:"providersite"` - ServiceContact struct { - IndividualName string `xml:"ows:IndividualName" yaml:"individualname"` - PositionName string `xml:"ows:PositionName" yaml:"positionname"` - ContactInfo struct { - Text string `xml:",chardata"` - Phone struct { - Voice string `xml:"ows:Voice" yaml:"voice"` - Facsimile string `xml:"ows:Facsimile" yaml:"facsmile"` - } `xml:"ows:Phone" yaml:"phone"` - Address struct { - DeliveryPoint string `xml:"ows:DeliveryPoint" yaml:"deliverypoint"` - City string `xml:"ows:City" yaml:"city"` - AdministrativeArea string `xml:"ows:AdministrativeArea" yaml:"administrativearea"` - PostalCode string `xml:"ows:PostalCode" yaml:"postalcode"` - Country string `xml:"ows:Country" yaml:"country"` - ElectronicMailAddress string `xml:"ows:ElectronicMailAddress" yaml:"electronicmailaddress"` - } `xml:"ows:Address" yaml:"address"` - OnlineResource struct { - Type string `xml:"xlink:type,attr" yaml:"type"` - Href string `xml:"xlink:href,attr" yaml:"href"` - } `xml:"ows:OnlineResource" yaml:"onlineresource"` - HoursOfService string `xml:"ows:HoursOfService" yaml:"hoursofservice"` - ContactInstructions string `xml:"ows:ContactInstructions" yaml:"contactinstructions"` - } `xml:"ows:ContactInfo" yaml:"contactinfo"` - Role string `xml:"ows:Role" yaml:"role"` - } `xml:"ows:ServiceContact" yaml:"servicecontact"` -} +package response + +import ( + "encoding/xml" + "regexp" + + "github.com/pdok/ogc-specifications/pkg/ows" + "github.com/pdok/ogc-specifications/pkg/wfs200/capabilities" +) + +// +const ( + getcapabilities = `GetCapabilities` +) + +// +const ( + Service = `WFS` + Version = `2.0.0` +) + +// Contains the WFS200 struct + +// Type function needed for the interface +func (gc *GetCapabilities) Type() string { + return getcapabilities +} + +// Service function needed for the interface +func (gc *GetCapabilities) Service() string { + return Service +} + +// Version function needed for the interface +func (gc *GetCapabilities) Version() string { + return Version +} + +// Validate function of the wfs200 spec +func (gc *GetCapabilities) Validate() ows.Exception { + return nil +} + +// BuildXML builds a GetCapabilities response object +func (gc *GetCapabilities) BuildXML() []byte { + si, _ := xml.MarshalIndent(gc, "", "") + re := regexp.MustCompile(`><.*>`) + return []byte(xml.Header + re.ReplaceAllString(string(si), "/>")) +} + +// GetCapabilities base struct +type GetCapabilities struct { + XMLName xml.Name `xml:"wfs:WFS_Capabilities"` + Namespaces `yaml:"namespaces"` + ServiceIdentification ServiceIdentification `xml:"ows:ServiceIdentification" yaml:"serviceidentification"` + ServiceProvider ServiceProvider `xml:"ows:ServiceProvider" yaml:"serviceprovider"` + OperationsMetadata capabilities.OperationsMetadata `xml:"ows:OperationsMetadata" yaml:"operationsmetadata"` + FeatureTypeList capabilities.FeatureTypeList `xml:"wfs:FeatureTypeList" yaml:"featuretypelist"` + FilterCapabilities capabilities.FilterCapabilities `xml:"fes:Filter_Capabilities" yaml:"filtercapabilities"` +} + +// Namespaces struct containing the namespaces needed for the XML document +type Namespaces struct { + XmlnsGML string `xml:"xmlns:gml,attr" yaml:"gml"` //http://www.opengis.net/gml/3.2 + XmlnsWFS string `xml:"xmlns:wfs,attr" yaml:"wfs"` //http://www.opengis.net/wfs/2.0 + XmlnsOWS string `xml:"xmlns:ows,attr" yaml:"ows"` //http://www.opengis.net/ows/1.1 + XmlnsXlink string `xml:"xmlns:xlink,attr" yaml:"xlink"` //http://www.w3.org/1999/xlink + XmlnsXSI string `xml:"xmlns:xsi,attr" yaml:"xsi"` //http://www.w3.org/2001/XMLSchema-instance + XmlnsFes string `xml:"xmlns:fes,attr" yaml:"fes"` //http://www.opengis.net/fes/2.0 + XmlnsInspireCommon string `xml:"xmlns:inspire_common,attr,omitempty" yaml:"inspirecommon,omitempty"` //http://inspire.ec.europa.eu/schemas/common/1.0 + XmlnsInspireDls string `xml:"xmlns:inspire_dls,attr,omitempty" yaml:"inspiredls,omitempty"` //http://inspire.ec.europa.eu/schemas/inspire_dls/1.0 + XmlnsPrefix string `xml:"xmlns:{{.Prefix}},attr" yaml:"prefix"` //namespace_uri placeholder + Version string `xml:"version,attr" yaml:"version"` + SchemaLocation string `xml:"xsi:schemaLocation,attr" yaml:"schemalocation"` +} + +// ServiceIdentification struct should only be fill by the "template" configuration wfs200.yaml +type ServiceIdentification struct { + XMLName xml.Name `xml:"ows:ServiceIdentification"` + Title string `xml:"ows:Title" yaml:"title"` + Abstract string `xml:"ows:Abstract" yaml:"abstract"` + Keywords *ows.Keywords `xml:"ows:Keywords" yaml:"keywords"` + ServiceType struct { + Text string `xml:",chardata" yaml:"text"` + CodeSpace string `xml:"codeSpace,attr" yaml:"codespace"` + } `xml:"ows:ServiceType"` + ServiceTypeVersion string `xml:"ows:ServiceTypeVersion" yaml:"servicetypeversion"` + Fees string `xml:"ows:Fees" yaml:"fees"` + AccessConstraints string `xml:"ows:AccessConstraints" yaml:"accesscontraints"` +} + +// ServiceProvider struct containing the provider/organization information should only be fill by the "template" configuration wfs200.yaml +type ServiceProvider struct { + XMLName xml.Name `xml:"ows:ServiceProvider"` + ProviderName string `xml:"ows:ProviderName" yaml:"providername"` + ProviderSite struct { + Type string `xml:"xlink:type,attr" yaml:"type"` + Href string `xml:"xlink:href,attr" yaml:"href"` + } `xml:"ows:ProviderSite" yaml:"providersite"` + ServiceContact struct { + IndividualName string `xml:"ows:IndividualName" yaml:"individualname"` + PositionName string `xml:"ows:PositionName" yaml:"positionname"` + ContactInfo struct { + Text string `xml:",chardata"` + Phone struct { + Voice string `xml:"ows:Voice" yaml:"voice"` + Facsimile string `xml:"ows:Facsimile" yaml:"facsmile"` + } `xml:"ows:Phone" yaml:"phone"` + Address struct { + DeliveryPoint string `xml:"ows:DeliveryPoint" yaml:"deliverypoint"` + City string `xml:"ows:City" yaml:"city"` + AdministrativeArea string `xml:"ows:AdministrativeArea" yaml:"administrativearea"` + PostalCode string `xml:"ows:PostalCode" yaml:"postalcode"` + Country string `xml:"ows:Country" yaml:"country"` + ElectronicMailAddress string `xml:"ows:ElectronicMailAddress" yaml:"electronicmailaddress"` + } `xml:"ows:Address" yaml:"address"` + OnlineResource struct { + Type string `xml:"xlink:type,attr" yaml:"type"` + Href string `xml:"xlink:href,attr" yaml:"href"` + } `xml:"ows:OnlineResource" yaml:"onlineresource"` + HoursOfService string `xml:"ows:HoursOfService" yaml:"hoursofservice"` + ContactInstructions string `xml:"ows:ContactInstructions" yaml:"contactinstructions"` + } `xml:"ows:ContactInfo" yaml:"contactinfo"` + Role string `xml:"ows:Role" yaml:"role"` + } `xml:"ows:ServiceContact" yaml:"servicecontact"` +} diff --git a/pkg/wms130/request/getmap.go b/pkg/wms130/request/getmap.go index aaa425f..8d97ba1 100644 --- a/pkg/wms130/request/getmap.go +++ b/pkg/wms130/request/getmap.go @@ -374,7 +374,7 @@ type StyledLayerDescriptor struct { } // Validate the StyledLayerDescriptor -func (sld StyledLayerDescriptor) Validate(capabilities *capabilities.Capability) ows.Exception { +func (sld *StyledLayerDescriptor) Validate(capabilities capabilities.Capability) ows.Exception { var unknown []string for _, l := range sld.GetNamedLayers() { found := false diff --git a/pkg/wms130/request/getmap_test.go b/pkg/wms130/request/getmap_test.go index 91531a2..5b65064 100644 --- a/pkg/wms130/request/getmap_test.go +++ b/pkg/wms130/request/getmap_test.go @@ -1,638 +1,638 @@ -package request - -import ( - "encoding/xml" - "net/url" - "testing" - - "github.com/pdok/ogc-specifications/pkg/ows" - "github.com/pdok/ogc-specifications/pkg/wms130/capabilities" - "github.com/pdok/ogc-specifications/pkg/wms130/exception" -) - -func sp(s string) *string { - return &s -} - -func ip(i int) *int { - return &i -} - -func TestGetMapType(t *testing.T) { - dft := GetMap{} - if dft.Type() != `GetMap` { - t.Errorf("test: %d, expected: %s,\n got: %s", 0, `GetMap`, dft.Type()) - } -} - -func TestBuildStyledLayerDescriptor(t *testing.T) { - var tests = []struct { - layers []string - styles []string - sld StyledLayerDescriptor - Error ows.Exception - }{ - 0: {layers: []string{"layer1", "layer2"}, styles: []string{"style1", "style2"}, sld: StyledLayerDescriptor{NamedLayer: []NamedLayer{{Name: "layer1", NamedStyle: &NamedStyle{Name: "style1"}}, {Name: "layer2", NamedStyle: &NamedStyle{Name: "style2"}}}}}, - 1: {layers: []string{"layer1", "layer2"}, styles: []string{"style1", "style2", "style3"}, Error: exception.StyleNotDefined()}, - 2: {layers: []string{"layer1", "layer2"}, styles: []string{"style1"}, Error: exception.StyleNotDefined()}, - 3: {layers: []string{"layer1", "layer2"}, sld: StyledLayerDescriptor{NamedLayer: []NamedLayer{{Name: "layer1"}, {Name: "layer2"}}}}, - } - - for k, test := range tests { - result, err := buildStyledLayerDescriptor(test.layers, test.styles) - - if err != nil { - if test.Error == nil || err != test.Error { - t.Errorf("test: %d, expected: %+v \ngot: %+v", k, test.Error, err) - } - } - - for s := range result.NamedLayer { - rl := &result.NamedLayer[s] - tl := &test.sld.NamedLayer[s] - if rl.Name != tl.Name { - t.Errorf("test: %d, expected: %+v \ngot: %+v", k, tl.Name, rl.Name) - } - if rl.NamedStyle != nil && tl.NamedStyle != nil { - if rl.NamedStyle.Name != tl.NamedStyle.Name { - t.Errorf("test: %d, expected: %+v \ngot: %+v", k, tl.NamedStyle.Name, rl.NamedStyle.Name) - } - } - } - } -} - -func TestValidateStyledLayerDescriptor(t *testing.T) { - var tests = []struct { - capabilities *capabilities.Capability - sld StyledLayerDescriptor - exception ows.Exception - }{ - 0: { - capabilities: &capabilities.Capability{ - Layer: []capabilities.Layer{ - {Name: sp(`layer1`)}, - {Name: sp(`layer2`)}, - }, - }, - sld: StyledLayerDescriptor{NamedLayer: []NamedLayer{{Name: "layer1"}, {Name: "layer2"}}}, - }, - 1: { - capabilities: &capabilities.Capability{ - Layer: []capabilities.Layer{ - {Name: sp(`layer2`)}, - {Name: sp(`layer3`)}, - }, - }, - sld: StyledLayerDescriptor{NamedLayer: []NamedLayer{{Name: "layer1"}, {Name: "layer2"}}}, - exception: exception.LayerNotDefined(`layer1`), - }, - } - - for k, test := range tests { - err := test.sld.Validate(test.capabilities) - if test.exception != err { - t.Errorf("test exception: %d, expected: %s ,\n got: %s", k, test.exception.Error(), err.Error()) - } - - } -} - -func TestGetMapParseXML(t *testing.T) { - var tests = []struct { - Body []byte - Excepted GetMap - Error ows.Exception - }{ - // GetMap schemas.opengis.net/sld/1.1.0/example_getmap.xml example request - 0: {Body: []byte(` - - - Rivers - - CenterLine - - - - Roads - - CenterLine - - - - Houses - - Outline - - - - EPSG:4326 - - -180.0 -90.0 - 180.0 90.0 - - - - 1024 - 512 - - image/jpeg - false - - XML - `), - Excepted: GetMap{ - BaseRequest: BaseRequest{ - Version: "1.3.0", - Attr: ows.XMLAttribute{ - xml.Attr{Name: xml.Name{Local: "xmlns"}, Value: "http://www.opengis.net/sld"}, - xml.Attr{Name: xml.Name{Space: "xmlns", Local: "gml"}, Value: "http://www.opengis.net/gml"}, - xml.Attr{Name: xml.Name{Space: "xmlns", Local: "ogc"}, Value: "http://www.opengis.net/ogc"}, - xml.Attr{Name: xml.Name{Space: "xmlns", Local: "ows"}, Value: "http://www.opengis.net/ows"}, - xml.Attr{Name: xml.Name{Space: "xmlns", Local: "se"}, Value: "http://www.opengis.net/se"}, - xml.Attr{Name: xml.Name{Space: "xmlns", Local: "wms"}, Value: "http://www.opengis.net/wms"}, - xml.Attr{Name: xml.Name{Space: "xmlns", Local: "xsi"}, Value: "http://www.w3.org/2001/XMLSchema-instance"}, - xml.Attr{Name: xml.Name{Space: "http://www.w3.org/2001/XMLSchema-instance", Local: "schemaLocation"}, Value: "http://www.opengis.net/sld GetMap.xsd"}, - }}, - StyledLayerDescriptor: StyledLayerDescriptor{ - Version: "1.1.0", - NamedLayer: []NamedLayer{ - {Name: "Rivers", NamedStyle: &NamedStyle{Name: "CenterLine"}}, - {Name: "Roads", NamedStyle: &NamedStyle{Name: "CenterLine"}}, - {Name: "Houses", NamedStyle: &NamedStyle{Name: "Outline"}}, - }}, - CRS: "EPSG:4326", - BoundingBox: ows.BoundingBox{ - Crs: "http://www.opengis.net/gml/srs/epsg.xml#4326", - LowerCorner: [2]float64{-180.0, -90.0}, - UpperCorner: [2]float64{180.0, 90.0}, - }, - Output: Output{ - Size: Size{Width: 1024, Height: 512}, - Format: "image/jpeg", - Transparent: sp("false")}, - Exceptions: sp("XML"), - }, - }, - 1: {Body: []byte(``), Error: ows.MissingParameterValue()}, - 2: {Body: []byte(``), Excepted: GetMap{}}, - } - for k, n := range tests { - var gm GetMap - err := gm.ParseXML(n.Body) - if err != nil { - if err.Error() != n.Error.Error() { - t.Errorf("test: %d, expected: %s,\n got: %s", k, n.Error, err) - } - } else { - compareGetMapObject(gm, n.Excepted, t, k) - } - } -} - -func TestGetLayerKVPValue(t *testing.T) { - var tests = []struct { - StyledLayerDescriptor StyledLayerDescriptor - Excepted string - }{ - 0: {StyledLayerDescriptor: StyledLayerDescriptor{ - NamedLayer: []NamedLayer{ - {Name: "Rivers"}, - {Name: "Roads"}, - {Name: "Houses"}, - }}, Excepted: "Rivers,Roads,Houses"}, - 1: {StyledLayerDescriptor: StyledLayerDescriptor{}, Excepted: ""}, - 2: {StyledLayerDescriptor: StyledLayerDescriptor{ - NamedLayer: []NamedLayer{ - {Name: "Rivers"}, - }}, Excepted: "Rivers"}, - } - - for k, n := range tests { - result := n.StyledLayerDescriptor.getLayerKVPValue() - if n.Excepted != result { - t.Errorf("test Exceptions: %d, expected: %v+ ,\n got: %v+", k, n.Excepted, result) - } - } -} - -func TestGetStyleKVPValue(t *testing.T) { - var tests = []struct { - StyledLayerDescriptor StyledLayerDescriptor - Excepted string - }{ - 0: {StyledLayerDescriptor: StyledLayerDescriptor{ - NamedLayer: []NamedLayer{ - {Name: "Rivers", NamedStyle: &NamedStyle{Name: "CenterLine"}}, - {Name: "Roads", NamedStyle: &NamedStyle{Name: "CenterLine"}}, - {Name: "Houses", NamedStyle: &NamedStyle{Name: "Outline"}}, - }}, Excepted: "CenterLine,CenterLine,Outline"}, - 1: {StyledLayerDescriptor: StyledLayerDescriptor{}, Excepted: ""}, - 2: {StyledLayerDescriptor: StyledLayerDescriptor{ - NamedLayer: []NamedLayer{ - {Name: "Rivers", NamedStyle: &NamedStyle{Name: "CenterLine"}}, - {Name: "Roads"}, - {Name: "Houses", NamedStyle: &NamedStyle{Name: "Outline"}}, - }}, Excepted: "CenterLine,,Outline"}, - // 4. This needs to fail in the validation step - 4: {StyledLayerDescriptor: StyledLayerDescriptor{ - NamedLayer: []NamedLayer{ - {NamedStyle: &NamedStyle{Name: "CenterLine"}}, - {NamedStyle: &NamedStyle{Name: "CenterLine"}}, - {NamedStyle: &NamedStyle{Name: "Outline"}}, - }}, Excepted: ""}, - } - - for k, n := range tests { - result := n.StyledLayerDescriptor.getStyleKVPValue() - if n.Excepted != result { - t.Errorf("test Exceptions: %d, expected: %v+ ,\n got: %v+", k, n.Excepted, result) - } - } -} - -func TestGetMapParseKVP(t *testing.T) { - var tests = []struct { - Query url.Values - Excepted GetMap - Exception ows.Exception - }{ - 0: {Query: map[string][]string{REQUEST: {getmap}, SERVICE: {Service}, VERSION: {Version}}, - Exception: ows.InvalidParameterValue(``, `boundingbox`), - }, - 1: {Query: url.Values{}, - Exception: ows.MissingParameterValue(VERSION)}, - //REQUEST=GetMap&SERVICE=WMS&VERSION=1.3.0&LAYERS=Rivers,Roads,Houses&STYLES=CenterLine,CenterLine,Outline&CRS=EPSG:4326&BBOX=-180.0,-90.0,180.0,90.0&WIDTH=1024&HEIGHT=512&FORMAT=image/jpeg&TRANSPARENT=FALSE&EXCEPTIONS=XML - 2: {Query: map[string][]string{REQUEST: {getmap}, SERVICE: {Service}, VERSION: {Version}, - LAYERS: {`Rivers,Roads,Houses`}, - STYLES: {`CenterLine,CenterLine,Outline`}, - CRS: {`EPSG:4326`}, - BBOX: {`-180.0,-90.0,180.0,90.0`}, - WIDTH: {`1024`}, - HEIGHT: {`512`}, - FORMAT: {`image/jpeg`}, - TRANSPARENT: {`FALSE`}, - EXCEPTIONS: {`XML`}, - BGCOLOR: {`0x7F7F7F`}, - }, - Excepted: GetMap{ - BaseRequest: BaseRequest{ - Version: "1.3.0", - }, - StyledLayerDescriptor: StyledLayerDescriptor{ - NamedLayer: []NamedLayer{ - {Name: "Rivers", NamedStyle: &NamedStyle{Name: "CenterLine"}}, - {Name: "Roads", NamedStyle: &NamedStyle{Name: "CenterLine"}}, - {Name: "Houses", NamedStyle: &NamedStyle{Name: "Outline"}}, - }}, - CRS: "EPSG:4326", - BoundingBox: ows.BoundingBox{ - LowerCorner: [2]float64{-180.0, -90.0}, - UpperCorner: [2]float64{180.0, 90.0}, - }, - Output: Output{ - Size: Size{Width: 1024, Height: 512}, - Format: "image/jpeg", - Transparent: sp("false"), - BGcolor: sp(`0x7F7F7F`)}, - Exceptions: sp("XML"), - }}, - } - for k, n := range tests { - var gm GetMap - err := gm.ParseKVP(n.Query) - if err != nil { - if err.Error() != n.Exception.Error() { - t.Errorf("test: %d, expected: %s,\n got: %s", k, n.Exception, err) - } - } else { - compareGetMapObject(gm, n.Excepted, t, k) - } - } -} - -func TestGetMapBuildKVP(t *testing.T) { - var tests = []struct { - Object GetMap - Excepted url.Values - Error ows.Exception - }{ - 0: {Object: GetMap{ - XMLName: xml.Name{Local: "GetMap"}, - BaseRequest: BaseRequest{ - Version: "1.3.0", - Service: "WMS", - }, - StyledLayerDescriptor: StyledLayerDescriptor{ - NamedLayer: []NamedLayer{ - {Name: "Rivers", NamedStyle: &NamedStyle{Name: "CenterLine"}}, - {Name: "Roads", NamedStyle: &NamedStyle{Name: "CenterLine"}}, - {Name: "Houses", NamedStyle: &NamedStyle{Name: "Outline"}}, - }}, - CRS: "EPSG:4326", - BoundingBox: ows.BoundingBox{ - LowerCorner: [2]float64{-180.0, -90.0}, - UpperCorner: [2]float64{180.0, 90.0}, - }, - Output: Output{ - Size: Size{Width: 1024, Height: 512}, - Format: "image/jpeg", - Transparent: sp("false")}, - Exceptions: sp("XML"), - }, Excepted: map[string][]string{ - LAYERS: {`Rivers,Roads,Houses`}, - STYLES: {`CenterLine,CenterLine,Outline`}, - CRS: {`EPSG:4326`}, - BBOX: {`-180.000000,-90.000000,180.000000,90.000000`}, - EXCEPTIONS: {`XML`}, - FORMAT: {`image/jpeg`}, - HEIGHT: {`512`}, - WIDTH: {`1024`}, - TRANSPARENT: {`false`}, - VERSION: {`1.3.0`}, - REQUEST: {`GetMap`}, - SERVICE: {`WMS`}, - }}, - 1: {Object: GetMap{ - CRS: "EPSG:4326", - BoundingBox: ows.BoundingBox{ - LowerCorner: [2]float64{-180.0, -90.0}, - UpperCorner: [2]float64{180.0, 90.0}, - }, - Exceptions: sp(`XML`), - }, - Excepted: map[string][]string{ - LAYERS: {``}, - STYLES: {``}, - CRS: {`EPSG:4326`}, - BBOX: {`-180.000000,-90.000000,180.000000,90.000000`}, - FORMAT: {``}, - HEIGHT: {`0`}, - WIDTH: {`0`}, - VERSION: {`1.3.0`}, - REQUEST: {`GetMap`}, - SERVICE: {`WMS`}, - EXCEPTIONS: {`XML`}, - }}, - } - - for k, n := range tests { - url := n.Object.BuildKVP() - if len(n.Excepted) != len(url) { - t.Errorf("test: %d, expected: %+v,\n got: %+v: ", k, n.Excepted, url) - } else { - for _, rid := range url { - found := false - for _, erid := range n.Excepted { - if rid[0] == erid[0] { - found = true - break - } - } - if !found { - t.Errorf("test: %d, expected: %+v,\n got: %+v: ", k, n.Excepted, url) - } - } - } - } -} - -func TestGetMapBuildXML(t *testing.T) { - var tests = []struct { - gm GetMap - result string - }{ - 0: {gm: GetMap{}, - result: ` - - - - - 0.000000 0.000000 - 0.000000 0.000000 - - - - 0 - 0 - - - -`}, - } - - for k, v := range tests { - body := v.gm.BuildXML() - - if string(body) != v.result { - t.Errorf("test: %d, Expected body %s but was not \n got: %s", k, v.result, string(body)) - } - } - -} - -func compareGetMapObject(result, expected GetMap, t *testing.T, k int) { - if result.BaseRequest.Version != expected.BaseRequest.Version { - t.Errorf("test Version: %d, expected: %s ,\n got: %s", k, expected.Version, result.Version) - } - - if len(expected.BaseRequest.Attr) == len(result.BaseRequest.Attr) { - c := false - for _, expectedAttr := range expected.BaseRequest.Attr { - for _, result := range result.BaseRequest.Attr { - if result.Name.Local == expectedAttr.Name.Local && result.Value == expectedAttr.Value { - c = true - } - } - if !c { - t.Errorf("test BaseRequest.Attr : %d, expected: %s ,\n got: %s", k, expected.BaseRequest.Attr, result.BaseRequest.Attr) - } - c = false - } - } else { - t.Errorf("test BaseRequest.Attr: %d, expected: %s ,\n got: %s", k, expected.BaseRequest.Attr, result.BaseRequest.Attr) - } - if len(expected.StyledLayerDescriptor.NamedLayer) == len(result.StyledLayerDescriptor.NamedLayer) { - c := false - for _, expected := range expected.StyledLayerDescriptor.NamedLayer { - for _, result := range result.StyledLayerDescriptor.NamedLayer { - if result.Name == expected.Name { - if *&result.NamedStyle.Name == *&expected.NamedStyle.Name { - c = true - } - } - } - if !c { - t.Errorf("test StyledLayerDescriptor.NamedLayer: %d, expected: %v+ ,\n got: %v+", k, expected, result.StyledLayerDescriptor.NamedLayer) - } - c = false - } - } else { - t.Errorf("test StyledLayerDescriptor: %d, expected: %v+ ,\n got: %v+", k, expected.StyledLayerDescriptor, result.StyledLayerDescriptor) - } - if expected.CRS != result.CRS { - t.Errorf("test CRS: %d, expected: %v+ ,\n got: %v+", k, expected.CRS, result.CRS) - } - if expected.BoundingBox != result.BoundingBox { - t.Errorf("test BoundingBox: %d, expected: %v+ ,\n got: %v+", k, expected.BoundingBox, result.BoundingBox) - } - if expected.Output.Size != result.Output.Size { - t.Errorf("test Output.Size: %d, expected: %v+ ,\n got: %v+", k, expected.Output.Size, result.Output.Size) - } - if expected.Exceptions != nil { - if *expected.Exceptions != *result.Exceptions { - t.Errorf("test Exceptions: %d, expected: %v+ ,\n got: %v+", k, *expected.Exceptions, *result.Exceptions) - } - } - if expected.Output.BGcolor != nil { - if *expected.Output.BGcolor != *result.Output.BGcolor { - t.Errorf("test BGcolor: %d, expected: %v+ ,\n got: %v+", k, *expected.Output.BGcolor, *result.Output.BGcolor) - } - } -} - -// ---------- -// Benchmarks -// ---------- - -func BenchmarkGetMapBuildKVP(b *testing.B) { - gm := GetMap{ - BaseRequest: BaseRequest{ - Version: "1.3.0", - Attr: ows.XMLAttribute{ - xml.Attr{Name: xml.Name{Local: "xmlns"}, Value: "http://www.opengis.net/sld"}, - xml.Attr{Name: xml.Name{Space: "xmlns", Local: "gml"}, Value: "http://www.opengis.net/gml"}, - xml.Attr{Name: xml.Name{Space: "xmlns", Local: "ogc"}, Value: "http://www.opengis.net/ogc"}, - xml.Attr{Name: xml.Name{Space: "xmlns", Local: "ows"}, Value: "http://www.opengis.net/ows"}, - xml.Attr{Name: xml.Name{Space: "xmlns", Local: "se"}, Value: "http://www.opengis.net/se"}, - xml.Attr{Name: xml.Name{Space: "xmlns", Local: "wms"}, Value: "http://www.opengis.net/wms"}, - xml.Attr{Name: xml.Name{Space: "xmlns", Local: "xsi"}, Value: "http://www.w3.org/2001/XMLSchema-instance"}, - xml.Attr{Name: xml.Name{Space: "http://www.w3.org/2001/XMLSchema-instance", Local: "schemaLocation"}, Value: "http://www.opengis.net/sld GetMap.xsd"}, - }}, - StyledLayerDescriptor: StyledLayerDescriptor{ - Version: "1.1.0", - NamedLayer: []NamedLayer{ - {Name: "Rivers", NamedStyle: &NamedStyle{Name: "CenterLine"}}, - {Name: "Roads", NamedStyle: &NamedStyle{Name: "CenterLine"}}, - {Name: "Houses", NamedStyle: &NamedStyle{Name: "Outline"}}, - }}, - CRS: "EPSG:4326", - BoundingBox: ows.BoundingBox{ - Crs: "http://www.opengis.net/gml/srs/epsg.xml#4326", - LowerCorner: [2]float64{-180.0, -90.0}, - UpperCorner: [2]float64{180.0, 90.0}, - }, - Output: Output{ - Size: Size{Width: 1024, Height: 512}, - Format: "image/jpeg", - Transparent: sp("false")}, - Exceptions: sp("XML"), - } - for i := 0; i < b.N; i++ { - gm.BuildKVP() - } -} - -func BenchmarkGetMapBuildXML(b *testing.B) { - gm := GetMap{ - BaseRequest: BaseRequest{ - Version: "1.3.0", - Attr: ows.XMLAttribute{ - xml.Attr{Name: xml.Name{Local: "xmlns"}, Value: "http://www.opengis.net/sld"}, - xml.Attr{Name: xml.Name{Space: "xmlns", Local: "gml"}, Value: "http://www.opengis.net/gml"}, - xml.Attr{Name: xml.Name{Space: "xmlns", Local: "ogc"}, Value: "http://www.opengis.net/ogc"}, - xml.Attr{Name: xml.Name{Space: "xmlns", Local: "ows"}, Value: "http://www.opengis.net/ows"}, - xml.Attr{Name: xml.Name{Space: "xmlns", Local: "se"}, Value: "http://www.opengis.net/se"}, - xml.Attr{Name: xml.Name{Space: "xmlns", Local: "wms"}, Value: "http://www.opengis.net/wms"}, - xml.Attr{Name: xml.Name{Space: "xmlns", Local: "xsi"}, Value: "http://www.w3.org/2001/XMLSchema-instance"}, - xml.Attr{Name: xml.Name{Space: "http://www.w3.org/2001/XMLSchema-instance", Local: "schemaLocation"}, Value: "http://www.opengis.net/sld GetMap.xsd"}, - }}, - StyledLayerDescriptor: StyledLayerDescriptor{ - Version: "1.1.0", - NamedLayer: []NamedLayer{ - {Name: "Rivers", NamedStyle: &NamedStyle{Name: "CenterLine"}}, - {Name: "Roads", NamedStyle: &NamedStyle{Name: "CenterLine"}}, - {Name: "Houses", NamedStyle: &NamedStyle{Name: "Outline"}}, - }}, - CRS: "EPSG:4326", - BoundingBox: ows.BoundingBox{ - Crs: "http://www.opengis.net/gml/srs/epsg.xml#4326", - LowerCorner: [2]float64{-180.0, -90.0}, - UpperCorner: [2]float64{180.0, 90.0}, - }, - Output: Output{ - Size: Size{Width: 1024, Height: 512}, - Format: "image/jpeg", - Transparent: sp("false")}, - Exceptions: sp("XML"), - } - for i := 0; i < b.N; i++ { - gm.BuildXML() - } -} - -func BenchmarkGetMapParseKVP(b *testing.B) { - kvp := map[string][]string{REQUEST: {getmap}, SERVICE: {Service}, VERSION: {Version}, - LAYERS: {`Rivers,Roads,Houses`}, - STYLES: {`CenterLine,CenterLine,Outline`}, - CRS: {`EPSG:4326`}, - BBOX: {`-180.0,-90.0,180.0,90.0`}, - WIDTH: {`1024`}, - HEIGHT: {`512`}, - FORMAT: {`image/jpeg`}, - TRANSPARENT: {`FALSE`}, - EXCEPTIONS: {`XML`}, - BGCOLOR: {`0x7F7F7F`}, - } - - for i := 0; i < b.N; i++ { - gm := GetMap{} - gm.ParseKVP(kvp) - } -} - -func BenchmarkGetMapParseXML(b *testing.B) { - doc := []byte(` - - - Rivers - - CenterLine - - - - Roads - - CenterLine - - - - Houses - - Outline - - - - EPSG:4326 - - -180.0 -90.0 - 180.0 90.0 - - - - 1024 - 512 - - image/jpeg - false - - XML -`) - - for i := 0; i < b.N; i++ { - gm := GetMap{} - gm.ParseXML(doc) - } -} +package request + +import ( + "encoding/xml" + "net/url" + "testing" + + "github.com/pdok/ogc-specifications/pkg/ows" + "github.com/pdok/ogc-specifications/pkg/wms130/capabilities" + "github.com/pdok/ogc-specifications/pkg/wms130/exception" +) + +func sp(s string) *string { + return &s +} + +func ip(i int) *int { + return &i +} + +func TestGetMapType(t *testing.T) { + dft := GetMap{} + if dft.Type() != `GetMap` { + t.Errorf("test: %d, expected: %s,\n got: %s", 0, `GetMap`, dft.Type()) + } +} + +func TestBuildStyledLayerDescriptor(t *testing.T) { + var tests = []struct { + layers []string + styles []string + sld StyledLayerDescriptor + Error ows.Exception + }{ + 0: {layers: []string{"layer1", "layer2"}, styles: []string{"style1", "style2"}, sld: StyledLayerDescriptor{NamedLayer: []NamedLayer{{Name: "layer1", NamedStyle: &NamedStyle{Name: "style1"}}, {Name: "layer2", NamedStyle: &NamedStyle{Name: "style2"}}}}}, + 1: {layers: []string{"layer1", "layer2"}, styles: []string{"style1", "style2", "style3"}, Error: exception.StyleNotDefined()}, + 2: {layers: []string{"layer1", "layer2"}, styles: []string{"style1"}, Error: exception.StyleNotDefined()}, + 3: {layers: []string{"layer1", "layer2"}, sld: StyledLayerDescriptor{NamedLayer: []NamedLayer{{Name: "layer1"}, {Name: "layer2"}}}}, + } + + for k, test := range tests { + result, err := buildStyledLayerDescriptor(test.layers, test.styles) + + if err != nil { + if test.Error == nil || err != test.Error { + t.Errorf("test: %d, expected: %+v \ngot: %+v", k, test.Error, err) + } + } + + for s := range result.NamedLayer { + rl := &result.NamedLayer[s] + tl := &test.sld.NamedLayer[s] + if rl.Name != tl.Name { + t.Errorf("test: %d, expected: %+v \ngot: %+v", k, tl.Name, rl.Name) + } + if rl.NamedStyle != nil && tl.NamedStyle != nil { + if rl.NamedStyle.Name != tl.NamedStyle.Name { + t.Errorf("test: %d, expected: %+v \ngot: %+v", k, tl.NamedStyle.Name, rl.NamedStyle.Name) + } + } + } + } +} + +func TestValidateStyledLayerDescriptor(t *testing.T) { + var tests = []struct { + capabilities capabilities.Capability + sld StyledLayerDescriptor + exception ows.Exception + }{ + 0: { + capabilities: capabilities.Capability{ + Layer: []capabilities.Layer{ + {Name: sp(`layer1`)}, + {Name: sp(`layer2`)}, + }, + }, + sld: StyledLayerDescriptor{NamedLayer: []NamedLayer{{Name: "layer1"}, {Name: "layer2"}}}, + }, + 1: { + capabilities: capabilities.Capability{ + Layer: []capabilities.Layer{ + {Name: sp(`layer2`)}, + {Name: sp(`layer3`)}, + }, + }, + sld: StyledLayerDescriptor{NamedLayer: []NamedLayer{{Name: "layer1"}, {Name: "layer2"}}}, + exception: exception.LayerNotDefined(`layer1`), + }, + } + + for k, test := range tests { + err := test.sld.Validate(test.capabilities) + if test.exception != err { + t.Errorf("test exception: %d, expected: %s ,\n got: %s", k, test.exception.Error(), err.Error()) + } + + } +} + +func TestGetMapParseXML(t *testing.T) { + var tests = []struct { + Body []byte + Excepted GetMap + Error ows.Exception + }{ + // GetMap schemas.opengis.net/sld/1.1.0/example_getmap.xml example request + 0: {Body: []byte(` + + + Rivers + + CenterLine + + + + Roads + + CenterLine + + + + Houses + + Outline + + + + EPSG:4326 + + -180.0 -90.0 + 180.0 90.0 + + + + 1024 + 512 + + image/jpeg + false + + XML + `), + Excepted: GetMap{ + BaseRequest: BaseRequest{ + Version: "1.3.0", + Attr: ows.XMLAttribute{ + xml.Attr{Name: xml.Name{Local: "xmlns"}, Value: "http://www.opengis.net/sld"}, + xml.Attr{Name: xml.Name{Space: "xmlns", Local: "gml"}, Value: "http://www.opengis.net/gml"}, + xml.Attr{Name: xml.Name{Space: "xmlns", Local: "ogc"}, Value: "http://www.opengis.net/ogc"}, + xml.Attr{Name: xml.Name{Space: "xmlns", Local: "ows"}, Value: "http://www.opengis.net/ows"}, + xml.Attr{Name: xml.Name{Space: "xmlns", Local: "se"}, Value: "http://www.opengis.net/se"}, + xml.Attr{Name: xml.Name{Space: "xmlns", Local: "wms"}, Value: "http://www.opengis.net/wms"}, + xml.Attr{Name: xml.Name{Space: "xmlns", Local: "xsi"}, Value: "http://www.w3.org/2001/XMLSchema-instance"}, + xml.Attr{Name: xml.Name{Space: "http://www.w3.org/2001/XMLSchema-instance", Local: "schemaLocation"}, Value: "http://www.opengis.net/sld GetMap.xsd"}, + }}, + StyledLayerDescriptor: StyledLayerDescriptor{ + Version: "1.1.0", + NamedLayer: []NamedLayer{ + {Name: "Rivers", NamedStyle: &NamedStyle{Name: "CenterLine"}}, + {Name: "Roads", NamedStyle: &NamedStyle{Name: "CenterLine"}}, + {Name: "Houses", NamedStyle: &NamedStyle{Name: "Outline"}}, + }}, + CRS: "EPSG:4326", + BoundingBox: ows.BoundingBox{ + Crs: "http://www.opengis.net/gml/srs/epsg.xml#4326", + LowerCorner: [2]float64{-180.0, -90.0}, + UpperCorner: [2]float64{180.0, 90.0}, + }, + Output: Output{ + Size: Size{Width: 1024, Height: 512}, + Format: "image/jpeg", + Transparent: sp("false")}, + Exceptions: sp("XML"), + }, + }, + 1: {Body: []byte(``), Error: ows.MissingParameterValue()}, + 2: {Body: []byte(``), Excepted: GetMap{}}, + } + for k, n := range tests { + var gm GetMap + err := gm.ParseXML(n.Body) + if err != nil { + if err.Error() != n.Error.Error() { + t.Errorf("test: %d, expected: %s,\n got: %s", k, n.Error, err) + } + } else { + compareGetMapObject(gm, n.Excepted, t, k) + } + } +} + +func TestGetLayerKVPValue(t *testing.T) { + var tests = []struct { + StyledLayerDescriptor StyledLayerDescriptor + Excepted string + }{ + 0: {StyledLayerDescriptor: StyledLayerDescriptor{ + NamedLayer: []NamedLayer{ + {Name: "Rivers"}, + {Name: "Roads"}, + {Name: "Houses"}, + }}, Excepted: "Rivers,Roads,Houses"}, + 1: {StyledLayerDescriptor: StyledLayerDescriptor{}, Excepted: ""}, + 2: {StyledLayerDescriptor: StyledLayerDescriptor{ + NamedLayer: []NamedLayer{ + {Name: "Rivers"}, + }}, Excepted: "Rivers"}, + } + + for k, n := range tests { + result := n.StyledLayerDescriptor.getLayerKVPValue() + if n.Excepted != result { + t.Errorf("test Exceptions: %d, expected: %v+ ,\n got: %v+", k, n.Excepted, result) + } + } +} + +func TestGetStyleKVPValue(t *testing.T) { + var tests = []struct { + StyledLayerDescriptor StyledLayerDescriptor + Excepted string + }{ + 0: {StyledLayerDescriptor: StyledLayerDescriptor{ + NamedLayer: []NamedLayer{ + {Name: "Rivers", NamedStyle: &NamedStyle{Name: "CenterLine"}}, + {Name: "Roads", NamedStyle: &NamedStyle{Name: "CenterLine"}}, + {Name: "Houses", NamedStyle: &NamedStyle{Name: "Outline"}}, + }}, Excepted: "CenterLine,CenterLine,Outline"}, + 1: {StyledLayerDescriptor: StyledLayerDescriptor{}, Excepted: ""}, + 2: {StyledLayerDescriptor: StyledLayerDescriptor{ + NamedLayer: []NamedLayer{ + {Name: "Rivers", NamedStyle: &NamedStyle{Name: "CenterLine"}}, + {Name: "Roads"}, + {Name: "Houses", NamedStyle: &NamedStyle{Name: "Outline"}}, + }}, Excepted: "CenterLine,,Outline"}, + // 4. This needs to fail in the validation step + 4: {StyledLayerDescriptor: StyledLayerDescriptor{ + NamedLayer: []NamedLayer{ + {NamedStyle: &NamedStyle{Name: "CenterLine"}}, + {NamedStyle: &NamedStyle{Name: "CenterLine"}}, + {NamedStyle: &NamedStyle{Name: "Outline"}}, + }}, Excepted: ""}, + } + + for k, n := range tests { + result := n.StyledLayerDescriptor.getStyleKVPValue() + if n.Excepted != result { + t.Errorf("test Exceptions: %d, expected: %v+ ,\n got: %v+", k, n.Excepted, result) + } + } +} + +func TestGetMapParseKVP(t *testing.T) { + var tests = []struct { + Query url.Values + Excepted GetMap + Exception ows.Exception + }{ + 0: {Query: map[string][]string{REQUEST: {getmap}, SERVICE: {Service}, VERSION: {Version}}, + Exception: ows.InvalidParameterValue(``, `boundingbox`), + }, + 1: {Query: url.Values{}, + Exception: ows.MissingParameterValue(VERSION)}, + //REQUEST=GetMap&SERVICE=WMS&VERSION=1.3.0&LAYERS=Rivers,Roads,Houses&STYLES=CenterLine,CenterLine,Outline&CRS=EPSG:4326&BBOX=-180.0,-90.0,180.0,90.0&WIDTH=1024&HEIGHT=512&FORMAT=image/jpeg&TRANSPARENT=FALSE&EXCEPTIONS=XML + 2: {Query: map[string][]string{REQUEST: {getmap}, SERVICE: {Service}, VERSION: {Version}, + LAYERS: {`Rivers,Roads,Houses`}, + STYLES: {`CenterLine,CenterLine,Outline`}, + CRS: {`EPSG:4326`}, + BBOX: {`-180.0,-90.0,180.0,90.0`}, + WIDTH: {`1024`}, + HEIGHT: {`512`}, + FORMAT: {`image/jpeg`}, + TRANSPARENT: {`FALSE`}, + EXCEPTIONS: {`XML`}, + BGCOLOR: {`0x7F7F7F`}, + }, + Excepted: GetMap{ + BaseRequest: BaseRequest{ + Version: "1.3.0", + }, + StyledLayerDescriptor: StyledLayerDescriptor{ + NamedLayer: []NamedLayer{ + {Name: "Rivers", NamedStyle: &NamedStyle{Name: "CenterLine"}}, + {Name: "Roads", NamedStyle: &NamedStyle{Name: "CenterLine"}}, + {Name: "Houses", NamedStyle: &NamedStyle{Name: "Outline"}}, + }}, + CRS: "EPSG:4326", + BoundingBox: ows.BoundingBox{ + LowerCorner: [2]float64{-180.0, -90.0}, + UpperCorner: [2]float64{180.0, 90.0}, + }, + Output: Output{ + Size: Size{Width: 1024, Height: 512}, + Format: "image/jpeg", + Transparent: sp("false"), + BGcolor: sp(`0x7F7F7F`)}, + Exceptions: sp("XML"), + }}, + } + for k, n := range tests { + var gm GetMap + err := gm.ParseKVP(n.Query) + if err != nil { + if err.Error() != n.Exception.Error() { + t.Errorf("test: %d, expected: %s,\n got: %s", k, n.Exception, err) + } + } else { + compareGetMapObject(gm, n.Excepted, t, k) + } + } +} + +func TestGetMapBuildKVP(t *testing.T) { + var tests = []struct { + Object GetMap + Excepted url.Values + Error ows.Exception + }{ + 0: {Object: GetMap{ + XMLName: xml.Name{Local: "GetMap"}, + BaseRequest: BaseRequest{ + Version: "1.3.0", + Service: "WMS", + }, + StyledLayerDescriptor: StyledLayerDescriptor{ + NamedLayer: []NamedLayer{ + {Name: "Rivers", NamedStyle: &NamedStyle{Name: "CenterLine"}}, + {Name: "Roads", NamedStyle: &NamedStyle{Name: "CenterLine"}}, + {Name: "Houses", NamedStyle: &NamedStyle{Name: "Outline"}}, + }}, + CRS: "EPSG:4326", + BoundingBox: ows.BoundingBox{ + LowerCorner: [2]float64{-180.0, -90.0}, + UpperCorner: [2]float64{180.0, 90.0}, + }, + Output: Output{ + Size: Size{Width: 1024, Height: 512}, + Format: "image/jpeg", + Transparent: sp("false")}, + Exceptions: sp("XML"), + }, Excepted: map[string][]string{ + LAYERS: {`Rivers,Roads,Houses`}, + STYLES: {`CenterLine,CenterLine,Outline`}, + CRS: {`EPSG:4326`}, + BBOX: {`-180.000000,-90.000000,180.000000,90.000000`}, + EXCEPTIONS: {`XML`}, + FORMAT: {`image/jpeg`}, + HEIGHT: {`512`}, + WIDTH: {`1024`}, + TRANSPARENT: {`false`}, + VERSION: {`1.3.0`}, + REQUEST: {`GetMap`}, + SERVICE: {`WMS`}, + }}, + 1: {Object: GetMap{ + CRS: "EPSG:4326", + BoundingBox: ows.BoundingBox{ + LowerCorner: [2]float64{-180.0, -90.0}, + UpperCorner: [2]float64{180.0, 90.0}, + }, + Exceptions: sp(`XML`), + }, + Excepted: map[string][]string{ + LAYERS: {``}, + STYLES: {``}, + CRS: {`EPSG:4326`}, + BBOX: {`-180.000000,-90.000000,180.000000,90.000000`}, + FORMAT: {``}, + HEIGHT: {`0`}, + WIDTH: {`0`}, + VERSION: {`1.3.0`}, + REQUEST: {`GetMap`}, + SERVICE: {`WMS`}, + EXCEPTIONS: {`XML`}, + }}, + } + + for k, n := range tests { + url := n.Object.BuildKVP() + if len(n.Excepted) != len(url) { + t.Errorf("test: %d, expected: %+v,\n got: %+v: ", k, n.Excepted, url) + } else { + for _, rid := range url { + found := false + for _, erid := range n.Excepted { + if rid[0] == erid[0] { + found = true + break + } + } + if !found { + t.Errorf("test: %d, expected: %+v,\n got: %+v: ", k, n.Excepted, url) + } + } + } + } +} + +func TestGetMapBuildXML(t *testing.T) { + var tests = []struct { + gm GetMap + result string + }{ + 0: {gm: GetMap{}, + result: ` + + + + + 0.000000 0.000000 + 0.000000 0.000000 + + + + 0 + 0 + + + +`}, + } + + for k, v := range tests { + body := v.gm.BuildXML() + + if string(body) != v.result { + t.Errorf("test: %d, Expected body %s but was not \n got: %s", k, v.result, string(body)) + } + } + +} + +func compareGetMapObject(result, expected GetMap, t *testing.T, k int) { + if result.BaseRequest.Version != expected.BaseRequest.Version { + t.Errorf("test Version: %d, expected: %s ,\n got: %s", k, expected.Version, result.Version) + } + + if len(expected.BaseRequest.Attr) == len(result.BaseRequest.Attr) { + c := false + for _, expectedAttr := range expected.BaseRequest.Attr { + for _, result := range result.BaseRequest.Attr { + if result.Name.Local == expectedAttr.Name.Local && result.Value == expectedAttr.Value { + c = true + } + } + if !c { + t.Errorf("test BaseRequest.Attr : %d, expected: %s ,\n got: %s", k, expected.BaseRequest.Attr, result.BaseRequest.Attr) + } + c = false + } + } else { + t.Errorf("test BaseRequest.Attr: %d, expected: %s ,\n got: %s", k, expected.BaseRequest.Attr, result.BaseRequest.Attr) + } + if len(expected.StyledLayerDescriptor.NamedLayer) == len(result.StyledLayerDescriptor.NamedLayer) { + c := false + for _, expected := range expected.StyledLayerDescriptor.NamedLayer { + for _, result := range result.StyledLayerDescriptor.NamedLayer { + if result.Name == expected.Name { + if *&result.NamedStyle.Name == *&expected.NamedStyle.Name { + c = true + } + } + } + if !c { + t.Errorf("test StyledLayerDescriptor.NamedLayer: %d, expected: %v+ ,\n got: %v+", k, expected, result.StyledLayerDescriptor.NamedLayer) + } + c = false + } + } else { + t.Errorf("test StyledLayerDescriptor: %d, expected: %v+ ,\n got: %v+", k, expected.StyledLayerDescriptor, result.StyledLayerDescriptor) + } + if expected.CRS != result.CRS { + t.Errorf("test CRS: %d, expected: %v+ ,\n got: %v+", k, expected.CRS, result.CRS) + } + if expected.BoundingBox != result.BoundingBox { + t.Errorf("test BoundingBox: %d, expected: %v+ ,\n got: %v+", k, expected.BoundingBox, result.BoundingBox) + } + if expected.Output.Size != result.Output.Size { + t.Errorf("test Output.Size: %d, expected: %v+ ,\n got: %v+", k, expected.Output.Size, result.Output.Size) + } + if expected.Exceptions != nil { + if *expected.Exceptions != *result.Exceptions { + t.Errorf("test Exceptions: %d, expected: %v+ ,\n got: %v+", k, *expected.Exceptions, *result.Exceptions) + } + } + if expected.Output.BGcolor != nil { + if *expected.Output.BGcolor != *result.Output.BGcolor { + t.Errorf("test BGcolor: %d, expected: %v+ ,\n got: %v+", k, *expected.Output.BGcolor, *result.Output.BGcolor) + } + } +} + +// ---------- +// Benchmarks +// ---------- + +func BenchmarkGetMapBuildKVP(b *testing.B) { + gm := GetMap{ + BaseRequest: BaseRequest{ + Version: "1.3.0", + Attr: ows.XMLAttribute{ + xml.Attr{Name: xml.Name{Local: "xmlns"}, Value: "http://www.opengis.net/sld"}, + xml.Attr{Name: xml.Name{Space: "xmlns", Local: "gml"}, Value: "http://www.opengis.net/gml"}, + xml.Attr{Name: xml.Name{Space: "xmlns", Local: "ogc"}, Value: "http://www.opengis.net/ogc"}, + xml.Attr{Name: xml.Name{Space: "xmlns", Local: "ows"}, Value: "http://www.opengis.net/ows"}, + xml.Attr{Name: xml.Name{Space: "xmlns", Local: "se"}, Value: "http://www.opengis.net/se"}, + xml.Attr{Name: xml.Name{Space: "xmlns", Local: "wms"}, Value: "http://www.opengis.net/wms"}, + xml.Attr{Name: xml.Name{Space: "xmlns", Local: "xsi"}, Value: "http://www.w3.org/2001/XMLSchema-instance"}, + xml.Attr{Name: xml.Name{Space: "http://www.w3.org/2001/XMLSchema-instance", Local: "schemaLocation"}, Value: "http://www.opengis.net/sld GetMap.xsd"}, + }}, + StyledLayerDescriptor: StyledLayerDescriptor{ + Version: "1.1.0", + NamedLayer: []NamedLayer{ + {Name: "Rivers", NamedStyle: &NamedStyle{Name: "CenterLine"}}, + {Name: "Roads", NamedStyle: &NamedStyle{Name: "CenterLine"}}, + {Name: "Houses", NamedStyle: &NamedStyle{Name: "Outline"}}, + }}, + CRS: "EPSG:4326", + BoundingBox: ows.BoundingBox{ + Crs: "http://www.opengis.net/gml/srs/epsg.xml#4326", + LowerCorner: [2]float64{-180.0, -90.0}, + UpperCorner: [2]float64{180.0, 90.0}, + }, + Output: Output{ + Size: Size{Width: 1024, Height: 512}, + Format: "image/jpeg", + Transparent: sp("false")}, + Exceptions: sp("XML"), + } + for i := 0; i < b.N; i++ { + gm.BuildKVP() + } +} + +func BenchmarkGetMapBuildXML(b *testing.B) { + gm := GetMap{ + BaseRequest: BaseRequest{ + Version: "1.3.0", + Attr: ows.XMLAttribute{ + xml.Attr{Name: xml.Name{Local: "xmlns"}, Value: "http://www.opengis.net/sld"}, + xml.Attr{Name: xml.Name{Space: "xmlns", Local: "gml"}, Value: "http://www.opengis.net/gml"}, + xml.Attr{Name: xml.Name{Space: "xmlns", Local: "ogc"}, Value: "http://www.opengis.net/ogc"}, + xml.Attr{Name: xml.Name{Space: "xmlns", Local: "ows"}, Value: "http://www.opengis.net/ows"}, + xml.Attr{Name: xml.Name{Space: "xmlns", Local: "se"}, Value: "http://www.opengis.net/se"}, + xml.Attr{Name: xml.Name{Space: "xmlns", Local: "wms"}, Value: "http://www.opengis.net/wms"}, + xml.Attr{Name: xml.Name{Space: "xmlns", Local: "xsi"}, Value: "http://www.w3.org/2001/XMLSchema-instance"}, + xml.Attr{Name: xml.Name{Space: "http://www.w3.org/2001/XMLSchema-instance", Local: "schemaLocation"}, Value: "http://www.opengis.net/sld GetMap.xsd"}, + }}, + StyledLayerDescriptor: StyledLayerDescriptor{ + Version: "1.1.0", + NamedLayer: []NamedLayer{ + {Name: "Rivers", NamedStyle: &NamedStyle{Name: "CenterLine"}}, + {Name: "Roads", NamedStyle: &NamedStyle{Name: "CenterLine"}}, + {Name: "Houses", NamedStyle: &NamedStyle{Name: "Outline"}}, + }}, + CRS: "EPSG:4326", + BoundingBox: ows.BoundingBox{ + Crs: "http://www.opengis.net/gml/srs/epsg.xml#4326", + LowerCorner: [2]float64{-180.0, -90.0}, + UpperCorner: [2]float64{180.0, 90.0}, + }, + Output: Output{ + Size: Size{Width: 1024, Height: 512}, + Format: "image/jpeg", + Transparent: sp("false")}, + Exceptions: sp("XML"), + } + for i := 0; i < b.N; i++ { + gm.BuildXML() + } +} + +func BenchmarkGetMapParseKVP(b *testing.B) { + kvp := map[string][]string{REQUEST: {getmap}, SERVICE: {Service}, VERSION: {Version}, + LAYERS: {`Rivers,Roads,Houses`}, + STYLES: {`CenterLine,CenterLine,Outline`}, + CRS: {`EPSG:4326`}, + BBOX: {`-180.0,-90.0,180.0,90.0`}, + WIDTH: {`1024`}, + HEIGHT: {`512`}, + FORMAT: {`image/jpeg`}, + TRANSPARENT: {`FALSE`}, + EXCEPTIONS: {`XML`}, + BGCOLOR: {`0x7F7F7F`}, + } + + for i := 0; i < b.N; i++ { + gm := GetMap{} + gm.ParseKVP(kvp) + } +} + +func BenchmarkGetMapParseXML(b *testing.B) { + doc := []byte(` + + + Rivers + + CenterLine + + + + Roads + + CenterLine + + + + Houses + + Outline + + + + EPSG:4326 + + -180.0 -90.0 + 180.0 90.0 + + + + 1024 + 512 + + image/jpeg + false + + XML +`) + + for i := 0; i < b.N; i++ { + gm := GetMap{} + gm.ParseXML(doc) + } +} From 1f0b66edcf31218b2c86d1d59465d6486d6b14aa Mon Sep 17 00:00:00 2001 From: Wouter Date: Tue, 28 Jul 2020 20:26:04 +0200 Subject: [PATCH 12/45] Validate() return Exception->Exceptions ([]Exception) --- pkg/ows/exception.go | 19 +++++++++++-------- pkg/ows/operationrequest.go | 4 ++-- pkg/ows/operationresponse.go | 4 ++-- pkg/wcs201/request/getcapabilities.go | 2 +- pkg/wcs201/response/getcapabilities.go | 2 +- pkg/wfs200/request/describefeaturetype.go | 2 +- pkg/wfs200/request/getcapabilities.go | 2 +- pkg/wfs200/request/getfeature.go | 2 +- pkg/wfs200/response/getcapabilities.go | 2 +- pkg/wms130/request/getcapabilities.go | 2 +- pkg/wms130/request/getfeatureinfo.go | 2 +- pkg/wms130/request/getmap.go | 2 +- pkg/wms130/response/getcapabilities.go | 2 +- pkg/wmts100/request/getcapabilities.go | 2 +- pkg/wmts100/response/getcapabilities.go | 2 +- 15 files changed, 27 insertions(+), 24 deletions(-) diff --git a/pkg/ows/exception.go b/pkg/ows/exception.go index ca849c8..2debbb3 100644 --- a/pkg/ows/exception.go +++ b/pkg/ows/exception.go @@ -11,18 +11,21 @@ const ( // ExceptionReport interface type ExceptionReport interface { - Report([]Exception) []byte + Report(Exceptions) []byte } +// Exceptions is a array of Exceptions +type Exceptions []Exception + // OWSExceptionReport struct type OWSExceptionReport struct { - XMLName xml.Name `xml:"ows:ExceptionReport" yaml:"exceptionreport"` - Ows string `xml:"xmlns:ows,attr,omitempty"` - Xsi string `xml:"xmlns:xsi,attr,omitempty"` - SchemaLocation string `xml:"xsi:schemaLocation,attr,omitempty"` - Version string `xml:"version,attr" yaml:"version"` - Language string `xml:"xml:lang,attr,omitempty" yaml:"lang,omitempty"` - Exception []Exception `xml:"Exception"` + XMLName xml.Name `xml:"ows:ExceptionReport" yaml:"exceptionreport"` + Ows string `xml:"xmlns:ows,attr,omitempty"` + Xsi string `xml:"xmlns:xsi,attr,omitempty"` + SchemaLocation string `xml:"xsi:schemaLocation,attr,omitempty"` + Version string `xml:"version,attr" yaml:"version"` + Language string `xml:"xml:lang,attr,omitempty" yaml:"lang,omitempty"` + Exception Exceptions `xml:"Exception"` } // Report returns OWSExceptionReport diff --git a/pkg/ows/operationrequest.go b/pkg/ows/operationrequest.go index 5388019..ce61fa8 100644 --- a/pkg/ows/operationrequest.go +++ b/pkg/ows/operationrequest.go @@ -5,7 +5,7 @@ import "net/url" // OperationRequest interface type OperationRequest interface { Type() string - Validate() Exception + Validate() Exceptions ParseXML([]byte) Exception ParseKVP(url.Values) Exception @@ -13,6 +13,6 @@ type OperationRequest interface { BuildKVP() url.Values // TODO YAML support - // ParseYAML([]byte) Exception + // ParseYAML([]byte) Exceptions // BuildYAML() []byte } diff --git a/pkg/ows/operationresponse.go b/pkg/ows/operationresponse.go index 7934a09..8a04174 100644 --- a/pkg/ows/operationresponse.go +++ b/pkg/ows/operationresponse.go @@ -5,9 +5,9 @@ type OperationResponse interface { Type() string Service() string Version() string - Validate() Exception + Validate() Exceptions - // ParseXML([]byte) Exception + // ParseXML([]byte) Exceptions ParseYAML([]byte) Exception BuildXML() []byte BuildYAML() []byte diff --git a/pkg/wcs201/request/getcapabilities.go b/pkg/wcs201/request/getcapabilities.go index 51dca90..846bf47 100644 --- a/pkg/wcs201/request/getcapabilities.go +++ b/pkg/wcs201/request/getcapabilities.go @@ -33,7 +33,7 @@ func (gc *GetCapabilities) Type() string { } // Validate returns GetCapabilities -func (gc *GetCapabilities) Validate() ows.Exception { +func (gc *GetCapabilities) Validate() ows.Exceptions { return nil } diff --git a/pkg/wcs201/response/getcapabilities.go b/pkg/wcs201/response/getcapabilities.go index 8b3af0f..db5944d 100644 --- a/pkg/wcs201/response/getcapabilities.go +++ b/pkg/wcs201/response/getcapabilities.go @@ -37,7 +37,7 @@ func (gc *GetCapabilities) Version() string { } // Validate function of the wfs200 spec -func (gc *GetCapabilities) Validate() ows.Exception { +func (gc *GetCapabilities) Validate() ows.Exceptions { return nil } diff --git a/pkg/wfs200/request/describefeaturetype.go b/pkg/wfs200/request/describefeaturetype.go index 53b1958..15f322d 100644 --- a/pkg/wfs200/request/describefeaturetype.go +++ b/pkg/wfs200/request/describefeaturetype.go @@ -23,7 +23,7 @@ func (dft *DescribeFeatureType) Type() string { } // Validate returns GetCapabilities -func (dft *DescribeFeatureType) Validate() ows.Exception { +func (dft *DescribeFeatureType) Validate() ows.Exceptions { return nil } diff --git a/pkg/wfs200/request/getcapabilities.go b/pkg/wfs200/request/getcapabilities.go index b3aa965..9ec4205 100644 --- a/pkg/wfs200/request/getcapabilities.go +++ b/pkg/wfs200/request/getcapabilities.go @@ -22,7 +22,7 @@ func (gc *GetCapabilities) Type() string { } // Validate returns GetCapabilities -func (gc *GetCapabilities) Validate() ows.Exception { +func (gc *GetCapabilities) Validate() ows.Exceptions { return nil } diff --git a/pkg/wfs200/request/getfeature.go b/pkg/wfs200/request/getfeature.go index 2070534..313a9df 100644 --- a/pkg/wfs200/request/getfeature.go +++ b/pkg/wfs200/request/getfeature.go @@ -48,7 +48,7 @@ func (gf *GetFeature) Type() string { } // Validate returns GetFeature -func (gf *GetFeature) Validate() ows.Exception { +func (gf *GetFeature) Validate() ows.Exceptions { return nil } diff --git a/pkg/wfs200/response/getcapabilities.go b/pkg/wfs200/response/getcapabilities.go index 3f542a4..45f1a97 100644 --- a/pkg/wfs200/response/getcapabilities.go +++ b/pkg/wfs200/response/getcapabilities.go @@ -37,7 +37,7 @@ func (gc *GetCapabilities) Version() string { } // Validate function of the wfs200 spec -func (gc *GetCapabilities) Validate() ows.Exception { +func (gc *GetCapabilities) Validate() ows.Exceptions { return nil } diff --git a/pkg/wms130/request/getcapabilities.go b/pkg/wms130/request/getcapabilities.go index 5812b14..5567f50 100644 --- a/pkg/wms130/request/getcapabilities.go +++ b/pkg/wms130/request/getcapabilities.go @@ -20,7 +20,7 @@ func (gc *GetCapabilities) Type() string { } // Validate returns GetCapabilities -func (gc *GetCapabilities) Validate() ows.Exception { +func (gc *GetCapabilities) Validate() ows.Exceptions { return nil } diff --git a/pkg/wms130/request/getfeatureinfo.go b/pkg/wms130/request/getfeatureinfo.go index fa19f18..20d07ee 100644 --- a/pkg/wms130/request/getfeatureinfo.go +++ b/pkg/wms130/request/getfeatureinfo.go @@ -39,7 +39,7 @@ func (gfi *GetFeatureInfo) Type() string { } // Validate returns GetFeatureInfo -func (gfi *GetFeatureInfo) Validate() ows.Exception { +func (gfi *GetFeatureInfo) Validate() ows.Exceptions { return nil } diff --git a/pkg/wms130/request/getmap.go b/pkg/wms130/request/getmap.go index 8d97ba1..fad2e25 100644 --- a/pkg/wms130/request/getmap.go +++ b/pkg/wms130/request/getmap.go @@ -43,7 +43,7 @@ func (gm *GetMap) Type() string { } // Validate returns GetMap -func (gm *GetMap) Validate() ows.Exception { +func (gm *GetMap) Validate() ows.Exceptions { return nil } diff --git a/pkg/wms130/response/getcapabilities.go b/pkg/wms130/response/getcapabilities.go index 76c88e9..3bf07af 100644 --- a/pkg/wms130/response/getcapabilities.go +++ b/pkg/wms130/response/getcapabilities.go @@ -37,7 +37,7 @@ func (gc *GetCapabilities) Version() string { } // Validate function of the wms130 spec -func (gc *GetCapabilities) Validate() ows.Exception { +func (gc *GetCapabilities) Validate() ows.Exceptions { return nil } diff --git a/pkg/wmts100/request/getcapabilities.go b/pkg/wmts100/request/getcapabilities.go index fc82d17..9d34bfa 100644 --- a/pkg/wmts100/request/getcapabilities.go +++ b/pkg/wmts100/request/getcapabilities.go @@ -33,7 +33,7 @@ func (gc *GetCapabilities) Type() string { } // Validate returns GetCapabilities -func (gc *GetCapabilities) Validate() ows.Exception { +func (gc *GetCapabilities) Validate() ows.Exceptions { return nil } diff --git a/pkg/wmts100/response/getcapabilities.go b/pkg/wmts100/response/getcapabilities.go index f8020e7..6cd4d20 100644 --- a/pkg/wmts100/response/getcapabilities.go +++ b/pkg/wmts100/response/getcapabilities.go @@ -35,7 +35,7 @@ func (gc *GetCapabilities) Version() string { } // Validate function of the wfs200 spec -func (gc *GetCapabilities) Validate() ows.Exception { +func (gc *GetCapabilities) Validate() ows.Exceptions { return nil } From b1330af250881c390b00688e041c0c71bea92526 Mon Sep 17 00:00:00 2001 From: Wouter Date: Tue, 28 Jul 2020 20:38:01 +0200 Subject: [PATCH 13/45] Capability interface and impl --- pkg/ows/capabilities.go | 11 +++++++++++ pkg/wcs201/capabilities/capabilities.go | 17 +++++++++++++++++ pkg/wcs201/response/getcapabilities.go | 8 +++----- pkg/wfs200/capabilities/capabilities.go | 17 +++++++++++++++++ pkg/wfs200/response/getcapabilities.go | 8 +++----- pkg/wms130/capabilities/capabilities.go | 10 ++++++++++ pkg/wmts100/capabilities/capabilities.go | 10 ++++++++++ 7 files changed, 71 insertions(+), 10 deletions(-) create mode 100644 pkg/ows/capabilities.go diff --git a/pkg/ows/capabilities.go b/pkg/ows/capabilities.go new file mode 100644 index 0000000..e3f9573 --- /dev/null +++ b/pkg/ows/capabilities.go @@ -0,0 +1,11 @@ +package ows + +// Capability interface +// return an error, if needed, not a Exception +// because this isn't a true OWS object only a base +// from which GetCapabilities can build and OperationRequest +// and OperationResponse can be validated against +type Capability interface { + ParseXML([]byte) error + ParseYAMl([]byte) error +} diff --git a/pkg/wcs201/capabilities/capabilities.go b/pkg/wcs201/capabilities/capabilities.go index 27e432e..f014d78 100644 --- a/pkg/wcs201/capabilities/capabilities.go +++ b/pkg/wcs201/capabilities/capabilities.go @@ -1,5 +1,22 @@ package capabilities +// ParseXML func +func (c *Capability) ParseXML(doc []byte) error { + return nil +} + +// ParseYML func +func (c *Capability) ParseYML(doc []byte) error { + return nil +} + +// Capability struct +type Capability struct { + OperationsMetadata OperationsMetadata `xml:"ows:OperationsMetadata" yaml:"operationsmetadata"` + ServiceMetadata ServiceMetadata `xml:"wcs:ServiceMetadata" yaml:"servicemetadata"` + Contents Contents `xml:"wcs:Contents" yaml:"contents"` +} + // OperationsMetadata struct for the WCS 2.0.1 type OperationsMetadata struct { Operation []Operation `xml:"ows:Operation" yaml:"operation"` diff --git a/pkg/wcs201/response/getcapabilities.go b/pkg/wcs201/response/getcapabilities.go index db5944d..ce7f332 100644 --- a/pkg/wcs201/response/getcapabilities.go +++ b/pkg/wcs201/response/getcapabilities.go @@ -52,11 +52,9 @@ func (gc *GetCapabilities) BuildXML() []byte { type GetCapabilities struct { XMLName xml.Name `xml:"wcs:Capabilities"` Namespaces `yaml:"namespaces"` - ServiceIdentification ServiceIdentification `xml:"ows:ServiceIdentification" yaml:"serviceidentification"` - ServiceProvider ServiceProvider `xml:"ows:ServiceProvider" yaml:"serviceprovider"` - OperationsMetadata capabilities.OperationsMetadata `xml:"ows:OperationsMetadata" yaml:"operationsmetadata"` - ServiceMetadata capabilities.ServiceMetadata `xml:"wcs:ServiceMetadata" yaml:"servicemetadata"` - Contents capabilities.Contents `xml:"wcs:Contents" yaml:"contents"` + ServiceIdentification ServiceIdentification `xml:"ows:ServiceIdentification" yaml:"serviceidentification"` + ServiceProvider ServiceProvider `xml:"ows:ServiceProvider" yaml:"serviceprovider"` + capabilities.Capability } // Namespaces struct containing the namespaces needed for the XML document diff --git a/pkg/wfs200/capabilities/capabilities.go b/pkg/wfs200/capabilities/capabilities.go index d8ef634..4e0d1cb 100644 --- a/pkg/wfs200/capabilities/capabilities.go +++ b/pkg/wfs200/capabilities/capabilities.go @@ -6,6 +6,23 @@ import ( "github.com/pdok/ogc-specifications/pkg/ows" ) +// ParseXML func +func (c *Capability) ParseXML(doc []byte) error { + return nil +} + +// ParseYML func +func (c *Capability) ParseYML(doc []byte) error { + return nil +} + +// Capability struct +type Capability struct { + OperationsMetadata OperationsMetadata `xml:"ows:OperationsMetadata" yaml:"operationsmetadata"` + FeatureTypeList FeatureTypeList `xml:"wfs:FeatureTypeList" yaml:"featuretypelist"` + FilterCapabilities FilterCapabilities `xml:"fes:Filter_Capabilities" yaml:"filtercapabilities"` +} + // Method in separated struct so to use it as a Pointer type Method struct { Type string `xml:"xlink:type,attr" yaml:"type"` diff --git a/pkg/wfs200/response/getcapabilities.go b/pkg/wfs200/response/getcapabilities.go index 45f1a97..e3579b3 100644 --- a/pkg/wfs200/response/getcapabilities.go +++ b/pkg/wfs200/response/getcapabilities.go @@ -52,11 +52,9 @@ func (gc *GetCapabilities) BuildXML() []byte { type GetCapabilities struct { XMLName xml.Name `xml:"wfs:WFS_Capabilities"` Namespaces `yaml:"namespaces"` - ServiceIdentification ServiceIdentification `xml:"ows:ServiceIdentification" yaml:"serviceidentification"` - ServiceProvider ServiceProvider `xml:"ows:ServiceProvider" yaml:"serviceprovider"` - OperationsMetadata capabilities.OperationsMetadata `xml:"ows:OperationsMetadata" yaml:"operationsmetadata"` - FeatureTypeList capabilities.FeatureTypeList `xml:"wfs:FeatureTypeList" yaml:"featuretypelist"` - FilterCapabilities capabilities.FilterCapabilities `xml:"fes:Filter_Capabilities" yaml:"filtercapabilities"` + ServiceIdentification ServiceIdentification `xml:"ows:ServiceIdentification" yaml:"serviceidentification"` + ServiceProvider ServiceProvider `xml:"ows:ServiceProvider" yaml:"serviceprovider"` + capabilities.Capability } // Namespaces struct containing the namespaces needed for the XML document diff --git a/pkg/wms130/capabilities/capabilities.go b/pkg/wms130/capabilities/capabilities.go index b6ced05..7f3ba81 100644 --- a/pkg/wms130/capabilities/capabilities.go +++ b/pkg/wms130/capabilities/capabilities.go @@ -2,6 +2,16 @@ package capabilities import "github.com/pdok/ogc-specifications/pkg/ows" +// ParseXML func +func (c *Capability) ParseXML(doc []byte) error { + return nil +} + +// ParseYML func +func (c *Capability) ParseYML(doc []byte) error { + return nil +} + // Capability base struct type Capability struct { Request Request `xml:"Request" yaml:"request"` diff --git a/pkg/wmts100/capabilities/capabilities.go b/pkg/wmts100/capabilities/capabilities.go index 617900f..7cc7a1f 100644 --- a/pkg/wmts100/capabilities/capabilities.go +++ b/pkg/wmts100/capabilities/capabilities.go @@ -1,5 +1,15 @@ package capabilities +// ParseXML func +func (c *Contents) ParseXML(doc []byte) error { + return nil +} + +// ParseYML func +func (c *Contents) ParseYML(doc []byte) error { + return nil +} + // Contents struct for the WMTS 1.0.0 type Contents struct { Layer []Layer `xml:"Layer" yaml:"layer"` From 4a5fd2c964d4b59afefa7ffb067351e973727421 Mon Sep 17 00:00:00 2001 From: Wouter Date: Tue, 28 Jul 2020 20:49:09 +0200 Subject: [PATCH 14/45] update OperationRequest interface Validate with input value Capability --- pkg/ows/operationrequest.go | 2 +- pkg/wcs201/request/getcapabilities.go | 3 ++- pkg/wfs200/request/describefeaturetype.go | 3 ++- pkg/wfs200/request/getcapabilities.go | 3 ++- pkg/wfs200/request/getfeature.go | 3 ++- pkg/wms130/request/getcapabilities.go | 3 ++- pkg/wms130/request/getfeatureinfo.go | 3 ++- pkg/wms130/request/getmap.go | 2 +- pkg/wmts100/request/getcapabilities.go | 3 ++- 9 files changed, 16 insertions(+), 9 deletions(-) diff --git a/pkg/ows/operationrequest.go b/pkg/ows/operationrequest.go index ce61fa8..f0171a1 100644 --- a/pkg/ows/operationrequest.go +++ b/pkg/ows/operationrequest.go @@ -5,7 +5,7 @@ import "net/url" // OperationRequest interface type OperationRequest interface { Type() string - Validate() Exceptions + Validate(Capability) Exceptions ParseXML([]byte) Exception ParseKVP(url.Values) Exception diff --git a/pkg/wcs201/request/getcapabilities.go b/pkg/wcs201/request/getcapabilities.go index 846bf47..45d6153 100644 --- a/pkg/wcs201/request/getcapabilities.go +++ b/pkg/wcs201/request/getcapabilities.go @@ -7,6 +7,7 @@ import ( "strings" "github.com/pdok/ogc-specifications/pkg/ows" + "github.com/pdok/ogc-specifications/pkg/wcs201/capabilities" ) // @@ -33,7 +34,7 @@ func (gc *GetCapabilities) Type() string { } // Validate returns GetCapabilities -func (gc *GetCapabilities) Validate() ows.Exceptions { +func (gc *GetCapabilities) Validate(capabilites capabilities.Capability) ows.Exceptions { return nil } diff --git a/pkg/wfs200/request/describefeaturetype.go b/pkg/wfs200/request/describefeaturetype.go index 15f322d..b4942de 100644 --- a/pkg/wfs200/request/describefeaturetype.go +++ b/pkg/wfs200/request/describefeaturetype.go @@ -6,6 +6,7 @@ import ( "github.com/pdok/ogc-specifications/pkg/ows" "github.com/pdok/ogc-specifications/pkg/utils" + "github.com/pdok/ogc-specifications/pkg/wfs200/capabilities" "regexp" "strings" @@ -23,7 +24,7 @@ func (dft *DescribeFeatureType) Type() string { } // Validate returns GetCapabilities -func (dft *DescribeFeatureType) Validate() ows.Exceptions { +func (dft *DescribeFeatureType) Validate(capabilites capabilities.Capability) ows.Exceptions { return nil } diff --git a/pkg/wfs200/request/getcapabilities.go b/pkg/wfs200/request/getcapabilities.go index 9ec4205..8da3d76 100644 --- a/pkg/wfs200/request/getcapabilities.go +++ b/pkg/wfs200/request/getcapabilities.go @@ -7,6 +7,7 @@ import ( "strings" "github.com/pdok/ogc-specifications/pkg/ows" + "github.com/pdok/ogc-specifications/pkg/wfs200/capabilities" ) // Type and Version as constant @@ -22,7 +23,7 @@ func (gc *GetCapabilities) Type() string { } // Validate returns GetCapabilities -func (gc *GetCapabilities) Validate() ows.Exceptions { +func (gc *GetCapabilities) Validate(capabilites capabilities.Capability) ows.Exceptions { return nil } diff --git a/pkg/wfs200/request/getfeature.go b/pkg/wfs200/request/getfeature.go index 313a9df..50b69eb 100644 --- a/pkg/wfs200/request/getfeature.go +++ b/pkg/wfs200/request/getfeature.go @@ -10,6 +10,7 @@ import ( "github.com/pdok/ogc-specifications/pkg/ows" "github.com/pdok/ogc-specifications/pkg/utils" + "github.com/pdok/ogc-specifications/pkg/wfs200/capabilities" "github.com/pdok/ogc-specifications/pkg/wfs200/exception" ) @@ -48,7 +49,7 @@ func (gf *GetFeature) Type() string { } // Validate returns GetFeature -func (gf *GetFeature) Validate() ows.Exceptions { +func (gf *GetFeature) Validate(capabilites capabilities.Capability) ows.Exceptions { return nil } diff --git a/pkg/wms130/request/getcapabilities.go b/pkg/wms130/request/getcapabilities.go index 5567f50..b2b13df 100644 --- a/pkg/wms130/request/getcapabilities.go +++ b/pkg/wms130/request/getcapabilities.go @@ -7,6 +7,7 @@ import ( "strings" "github.com/pdok/ogc-specifications/pkg/ows" + "github.com/pdok/ogc-specifications/pkg/wms130/capabilities" ) // @@ -20,7 +21,7 @@ func (gc *GetCapabilities) Type() string { } // Validate returns GetCapabilities -func (gc *GetCapabilities) Validate() ows.Exceptions { +func (gc *GetCapabilities) Validate(capabilites capabilities.Capability) ows.Exceptions { return nil } diff --git a/pkg/wms130/request/getfeatureinfo.go b/pkg/wms130/request/getfeatureinfo.go index 20d07ee..08fc2f9 100644 --- a/pkg/wms130/request/getfeatureinfo.go +++ b/pkg/wms130/request/getfeatureinfo.go @@ -9,6 +9,7 @@ import ( "github.com/pdok/ogc-specifications/pkg/ows" "github.com/pdok/ogc-specifications/pkg/utils" + "github.com/pdok/ogc-specifications/pkg/wms130/capabilities" "github.com/pdok/ogc-specifications/pkg/wms130/exception" ) @@ -39,7 +40,7 @@ func (gfi *GetFeatureInfo) Type() string { } // Validate returns GetFeatureInfo -func (gfi *GetFeatureInfo) Validate() ows.Exceptions { +func (gfi *GetFeatureInfo) Validate(capabilites capabilities.Capability) ows.Exceptions { return nil } diff --git a/pkg/wms130/request/getmap.go b/pkg/wms130/request/getmap.go index fad2e25..103bd63 100644 --- a/pkg/wms130/request/getmap.go +++ b/pkg/wms130/request/getmap.go @@ -43,7 +43,7 @@ func (gm *GetMap) Type() string { } // Validate returns GetMap -func (gm *GetMap) Validate() ows.Exceptions { +func (gm *GetMap) Validate(capabilites capabilities.Capability) ows.Exceptions { return nil } diff --git a/pkg/wmts100/request/getcapabilities.go b/pkg/wmts100/request/getcapabilities.go index 9d34bfa..08dac4d 100644 --- a/pkg/wmts100/request/getcapabilities.go +++ b/pkg/wmts100/request/getcapabilities.go @@ -7,6 +7,7 @@ import ( "strings" "github.com/pdok/ogc-specifications/pkg/ows" + "github.com/pdok/ogc-specifications/pkg/wcs201/capabilities" ) // @@ -33,7 +34,7 @@ func (gc *GetCapabilities) Type() string { } // Validate returns GetCapabilities -func (gc *GetCapabilities) Validate() ows.Exceptions { +func (gc *GetCapabilities) Validate(capabilites capabilities.Contents) ows.Exceptions { return nil } From 07111259b84c8c824a91b222b48f165d6388593d Mon Sep 17 00:00:00 2001 From: Wouter Date: Tue, 28 Jul 2020 20:51:01 +0200 Subject: [PATCH 15/45] GetLayerNames()->getLayerNames() made private --- pkg/wms130/capabilities/capabilities.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/wms130/capabilities/capabilities.go b/pkg/wms130/capabilities/capabilities.go index 7f3ba81..aab5488 100644 --- a/pkg/wms130/capabilities/capabilities.go +++ b/pkg/wms130/capabilities/capabilities.go @@ -61,7 +61,7 @@ func (c *Capability) GetLayerNames() []string { layers = append(layers, *l.Name) if l.Layer != nil { for _, n := range l.Layer { - u := n.GetLayerNames() + u := n.getLayerNames() layers = append(layers, u...) } } @@ -71,13 +71,13 @@ func (c *Capability) GetLayerNames() []string { } // GetLayerNames returns the available layers as []string -func (l *Layer) GetLayerNames() []string { +func (l *Layer) getLayerNames() []string { var layers []string layers = append(layers, *l.Name) if l.Layer != nil { for _, n := range l.Layer { - u := n.GetLayerNames() + u := n.getLayerNames() layers = append(layers, u...) } } From 0db0136a351bb434bda9a940778a8e372400edaa Mon Sep 17 00:00:00 2001 From: Wouter Date: Tue, 28 Jul 2020 20:54:41 +0200 Subject: [PATCH 16/45] renamed value --- pkg/wcs201/request/getcapabilities.go | 2 +- pkg/wfs200/request/describefeaturetype.go | 2 +- pkg/wfs200/request/getcapabilities.go | 2 +- pkg/wfs200/request/getfeature.go | 2 +- pkg/wms130/request/getcapabilities.go | 2 +- pkg/wms130/request/getfeatureinfo.go | 2 +- pkg/wms130/request/getmap.go | 8 ++++++-- pkg/wmts100/request/getcapabilities.go | 2 +- 8 files changed, 13 insertions(+), 9 deletions(-) diff --git a/pkg/wcs201/request/getcapabilities.go b/pkg/wcs201/request/getcapabilities.go index 45d6153..508a427 100644 --- a/pkg/wcs201/request/getcapabilities.go +++ b/pkg/wcs201/request/getcapabilities.go @@ -34,7 +34,7 @@ func (gc *GetCapabilities) Type() string { } // Validate returns GetCapabilities -func (gc *GetCapabilities) Validate(capabilites capabilities.Capability) ows.Exceptions { +func (gc *GetCapabilities) Validate(c capabilities.Capability) ows.Exceptions { return nil } diff --git a/pkg/wfs200/request/describefeaturetype.go b/pkg/wfs200/request/describefeaturetype.go index b4942de..1b4daec 100644 --- a/pkg/wfs200/request/describefeaturetype.go +++ b/pkg/wfs200/request/describefeaturetype.go @@ -24,7 +24,7 @@ func (dft *DescribeFeatureType) Type() string { } // Validate returns GetCapabilities -func (dft *DescribeFeatureType) Validate(capabilites capabilities.Capability) ows.Exceptions { +func (dft *DescribeFeatureType) Validate(c capabilities.Capability) ows.Exceptions { return nil } diff --git a/pkg/wfs200/request/getcapabilities.go b/pkg/wfs200/request/getcapabilities.go index 8da3d76..f09e7d0 100644 --- a/pkg/wfs200/request/getcapabilities.go +++ b/pkg/wfs200/request/getcapabilities.go @@ -23,7 +23,7 @@ func (gc *GetCapabilities) Type() string { } // Validate returns GetCapabilities -func (gc *GetCapabilities) Validate(capabilites capabilities.Capability) ows.Exceptions { +func (gc *GetCapabilities) Validate(c capabilities.Capability) ows.Exceptions { return nil } diff --git a/pkg/wfs200/request/getfeature.go b/pkg/wfs200/request/getfeature.go index 50b69eb..301ed6a 100644 --- a/pkg/wfs200/request/getfeature.go +++ b/pkg/wfs200/request/getfeature.go @@ -49,7 +49,7 @@ func (gf *GetFeature) Type() string { } // Validate returns GetFeature -func (gf *GetFeature) Validate(capabilites capabilities.Capability) ows.Exceptions { +func (gf *GetFeature) Validate(c capabilities.Capability) ows.Exceptions { return nil } diff --git a/pkg/wms130/request/getcapabilities.go b/pkg/wms130/request/getcapabilities.go index b2b13df..df7da01 100644 --- a/pkg/wms130/request/getcapabilities.go +++ b/pkg/wms130/request/getcapabilities.go @@ -21,7 +21,7 @@ func (gc *GetCapabilities) Type() string { } // Validate returns GetCapabilities -func (gc *GetCapabilities) Validate(capabilites capabilities.Capability) ows.Exceptions { +func (gc *GetCapabilities) Validate(c capabilities.Capability) ows.Exceptions { return nil } diff --git a/pkg/wms130/request/getfeatureinfo.go b/pkg/wms130/request/getfeatureinfo.go index 08fc2f9..fb27361 100644 --- a/pkg/wms130/request/getfeatureinfo.go +++ b/pkg/wms130/request/getfeatureinfo.go @@ -40,7 +40,7 @@ func (gfi *GetFeatureInfo) Type() string { } // Validate returns GetFeatureInfo -func (gfi *GetFeatureInfo) Validate(capabilites capabilities.Capability) ows.Exceptions { +func (gfi *GetFeatureInfo) Validate(c capabilities.Capability) ows.Exceptions { return nil } diff --git a/pkg/wms130/request/getmap.go b/pkg/wms130/request/getmap.go index 103bd63..3e9ea43 100644 --- a/pkg/wms130/request/getmap.go +++ b/pkg/wms130/request/getmap.go @@ -43,8 +43,12 @@ func (gm *GetMap) Type() string { } // Validate returns GetMap -func (gm *GetMap) Validate(capabilites capabilities.Capability) ows.Exceptions { - return nil +func (gm *GetMap) Validate(c capabilities.Capability) ows.Exceptions { + var exceptions ows.Exceptions + + gm.StyledLayerDescriptor.Validate(c) + + return exceptions } //GetMapKVP struct diff --git a/pkg/wmts100/request/getcapabilities.go b/pkg/wmts100/request/getcapabilities.go index 08dac4d..7339c08 100644 --- a/pkg/wmts100/request/getcapabilities.go +++ b/pkg/wmts100/request/getcapabilities.go @@ -34,7 +34,7 @@ func (gc *GetCapabilities) Type() string { } // Validate returns GetCapabilities -func (gc *GetCapabilities) Validate(capabilites capabilities.Contents) ows.Exceptions { +func (gc *GetCapabilities) Validate(c capabilities.Contents) ows.Exceptions { return nil } From 1a37e541a134771d4a486a3b85ff25b71f992c7d Mon Sep 17 00:00:00 2001 From: Wouter Date: Tue, 28 Jul 2020 21:05:03 +0200 Subject: [PATCH 17/45] update getmap validation test --- pkg/wms130/request/getmap.go | 11 ++++++++--- pkg/wms130/request/getmap_test.go | 23 ++++++++++++++++------- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/pkg/wms130/request/getmap.go b/pkg/wms130/request/getmap.go index 3e9ea43..7cd2725 100644 --- a/pkg/wms130/request/getmap.go +++ b/pkg/wms130/request/getmap.go @@ -46,7 +46,7 @@ func (gm *GetMap) Type() string { func (gm *GetMap) Validate(c capabilities.Capability) ows.Exceptions { var exceptions ows.Exceptions - gm.StyledLayerDescriptor.Validate(c) + exceptions = append(exceptions, gm.StyledLayerDescriptor.Validate(c)...) return exceptions } @@ -378,7 +378,7 @@ type StyledLayerDescriptor struct { } // Validate the StyledLayerDescriptor -func (sld *StyledLayerDescriptor) Validate(capabilities capabilities.Capability) ows.Exception { +func (sld *StyledLayerDescriptor) Validate(capabilities capabilities.Capability) ows.Exceptions { var unknown []string for _, l := range sld.GetNamedLayers() { found := false @@ -392,8 +392,13 @@ func (sld *StyledLayerDescriptor) Validate(capabilities capabilities.Capability) } } + var exceptions ows.Exceptions if len(unknown) > 0 { - return exception.LayerNotDefined(unknown...) + + for _, l := range unknown { + exceptions = append(exceptions, exception.LayerNotDefined(l)) + } + return exceptions } return nil diff --git a/pkg/wms130/request/getmap_test.go b/pkg/wms130/request/getmap_test.go index 5b65064..fa4c5b1 100644 --- a/pkg/wms130/request/getmap_test.go +++ b/pkg/wms130/request/getmap_test.go @@ -66,7 +66,7 @@ func TestValidateStyledLayerDescriptor(t *testing.T) { var tests = []struct { capabilities capabilities.Capability sld StyledLayerDescriptor - exception ows.Exception + exceptions []ows.Exception }{ 0: { capabilities: capabilities.Capability{ @@ -84,17 +84,26 @@ func TestValidateStyledLayerDescriptor(t *testing.T) { {Name: sp(`layer3`)}, }, }, - sld: StyledLayerDescriptor{NamedLayer: []NamedLayer{{Name: "layer1"}, {Name: "layer2"}}}, - exception: exception.LayerNotDefined(`layer1`), + sld: StyledLayerDescriptor{NamedLayer: []NamedLayer{{Name: "layer1"}, {Name: "layer2"}}}, + exceptions: []ows.Exception{exception.LayerNotDefined(`layer1`)}, }, } for k, test := range tests { - err := test.sld.Validate(test.capabilities) - if test.exception != err { - t.Errorf("test exception: %d, expected: %s ,\n got: %s", k, test.exception.Error(), err.Error()) + errs := test.sld.Validate(test.capabilities) + if len(errs) > 0 { + for _, err := range errs { + found := false + for _, exception := range test.exceptions { + if err == exception { + found = true + } + } + if !found { + t.Errorf("test exception: %d, expected one of: %s ,\n got: %s", k, test.exceptions, err.Error()) + } + } } - } } From d7443f7bc335565378a92b39a58c6659b3b4422a Mon Sep 17 00:00:00 2001 From: Wouter Date: Thu, 30 Jul 2020 14:02:53 +0200 Subject: [PATCH 18/45] various small spelling/code formatting --- pkg/wcs201/capabilities/capabilities.go | 6 +- pkg/wcs201/request/getcapabilities.go | 2 +- pkg/wfs200/exception/exception.go | 2 +- pkg/wfs200/request/describefeaturetype.go | 1 - pkg/wfs200/request/getfeature.go | 3 +- pkg/wms130/exception/exception.go | 4 +- pkg/wms130/request/common.go | 2 +- pkg/wms130/request/getfeatureinfo.go | 1 - pkg/wms130/request/getmap.go | 853 +++++++++++----------- pkg/wms130/request/getmap_test.go | 2 +- pkg/wms130/validation/validations.go | 2 - pkg/wms130/validation/validator_test.go | 1 - pkg/wmts100/capabilities/capabilities.go | 8 +- pkg/wmts100/response/getcapabilities.go | 2 +- 14 files changed, 441 insertions(+), 448 deletions(-) diff --git a/pkg/wcs201/capabilities/capabilities.go b/pkg/wcs201/capabilities/capabilities.go index f014d78..26c6303 100644 --- a/pkg/wcs201/capabilities/capabilities.go +++ b/pkg/wcs201/capabilities/capabilities.go @@ -23,7 +23,7 @@ type OperationsMetadata struct { ExtendedCapabilities *ExtendedCapabilities `xml:"ows:ExtendedCapabilities" yaml:"extendedcapabilities"` } -// Operation in struct for repeatablity +// Operation in struct for repeatability type Operation struct { Name string `xml:"name,attr" yaml:"name"` DCP struct { @@ -84,12 +84,12 @@ type ServiceMetadata struct { } `xml:"wcs:Extension"` } -// Contents in struct for repeatablity +// Contents in struct for repeatability type Contents struct { CoverageSummary []CoverageSummary `xml:"wcs:CoverageSummary"` } -// CoverageSummary in struct for repeatablity +// CoverageSummary in struct for repeatability type CoverageSummary struct { CoverageID string `xml:"wcs:CoverageId"` CoverageSubtype string `xml:"wcs:CoverageSubtype"` diff --git a/pkg/wcs201/request/getcapabilities.go b/pkg/wcs201/request/getcapabilities.go index 508a427..8e5679d 100644 --- a/pkg/wcs201/request/getcapabilities.go +++ b/pkg/wcs201/request/getcapabilities.go @@ -33,7 +33,7 @@ func (gc *GetCapabilities) Type() string { return getcapabilities } -// Validate returns GetCapabilities +// Validate validates the GetCapabilities struct func (gc *GetCapabilities) Validate(c capabilities.Capability) ows.Exceptions { return nil } diff --git a/pkg/wfs200/exception/exception.go b/pkg/wfs200/exception/exception.go index 487ed81..7972183 100644 --- a/pkg/wfs200/exception/exception.go +++ b/pkg/wfs200/exception/exception.go @@ -14,7 +14,7 @@ const ( ) // WFSExceptionReport struct -// TODO exception restucturing +// TODO exception restructuring type WFSExceptionReport struct { XMLName xml.Name `xml:"ExceptionReport" yaml:"exceptionreport"` Ows string `xml:"xmlns:ows,attr,omitempty"` diff --git a/pkg/wfs200/request/describefeaturetype.go b/pkg/wfs200/request/describefeaturetype.go index 1b4daec..31adb91 100644 --- a/pkg/wfs200/request/describefeaturetype.go +++ b/pkg/wfs200/request/describefeaturetype.go @@ -54,7 +54,6 @@ func (dft *DescribeFeatureType) ParseXML(doc []byte) ows.Exception { // ParseKVP builds a DescribeFeatureType object based on the available query parameters func (dft *DescribeFeatureType) ParseKVP(query url.Values) ows.Exception { - if len(query) == 0 { // When there are no query value we know that at least // the manadorty VERSION parameter is missing. diff --git a/pkg/wfs200/request/getfeature.go b/pkg/wfs200/request/getfeature.go index 301ed6a..fd79f3a 100644 --- a/pkg/wfs200/request/getfeature.go +++ b/pkg/wfs200/request/getfeature.go @@ -88,7 +88,6 @@ func (gf *GetFeature) ParseXML(doc []byte) ows.Exception { // ParseKVP builds a GetCapabilities object based on the available query parameters // All the keys from the query url.Values need to be UpperCase, this is done during the execution of the operations.ValidRequest() func (gf *GetFeature) ParseKVP(query url.Values) ows.Exception { - if len(query) == 0 { // When there are no query value we know that at least // the manadorty VERSION parameter is missing. @@ -489,7 +488,7 @@ type GeometryOperand struct { // Geometry struct for GeometryOperand geometries type Geometry struct { - SrsName string `xml:"srsName,attr" yaml:"srname"` + SrsName string `xml:"srsName,attr" yaml:"srsname"` Content string `xml:",innerxml"` } diff --git a/pkg/wms130/exception/exception.go b/pkg/wms130/exception/exception.go index 558e1a8..b0ea94c 100644 --- a/pkg/wms130/exception/exception.go +++ b/pkg/wms130/exception/exception.go @@ -14,7 +14,7 @@ const ( ) // WMSServiceExceptionReport struct -// TODO exception restucturing +// TODO exception restructuring type WMSServiceExceptionReport struct { XMLName xml.Name `xml:"ServiceExceptionReport" yaml:"serviceexceptionreport"` Version string `xml:"version,attr" yaml:"version"` @@ -117,7 +117,7 @@ func LayerNotQueryable(s ...string) WMSException { // InvalidPoint exception // i and j are strings so we can none int values in the exception func InvalidPoint(i, j string) WMSException { - // TODO provide giving WIDTH and HEIGTH values in Exception response + // TODO provide giving WIDTH and HEIGHT values in Exception response return WMSException{ ExceptionText: fmt.Sprintf("The parameters I and J are invalid, given: %s, %s", i, j), ExceptionCode: `InvalidPoint`, diff --git a/pkg/wms130/request/common.go b/pkg/wms130/request/common.go index 7fd2c91..ef45193 100644 --- a/pkg/wms130/request/common.go +++ b/pkg/wms130/request/common.go @@ -28,7 +28,7 @@ type BaseRequest struct { Attr ows.XMLAttribute `xml:",attr"` } -// ParseKVP builds a BaseRequest truct based on the given parameters +// ParseKVP builds a BaseRequest struct based on the given parameters func (b *BaseRequest) ParseKVP(query url.Values) ows.Exception { if len(query[SERVICE]) > 0 { // Service is optional, because it's implicit for a GetMap/GetFeatureInfo request diff --git a/pkg/wms130/request/getfeatureinfo.go b/pkg/wms130/request/getfeatureinfo.go index fb27361..74aadfe 100644 --- a/pkg/wms130/request/getfeatureinfo.go +++ b/pkg/wms130/request/getfeatureinfo.go @@ -72,7 +72,6 @@ func (gfi *GetFeatureInfo) ParseXML(body []byte) ows.Exception { // ParseKVP builds a GetFeatureInfo object based on the available query parameters func (gfi *GetFeatureInfo) ParseKVP(query url.Values) ows.Exception { - if len(query) == 0 { // When there are no query value we know that at least // the manadorty VERSION parameter is missing. diff --git a/pkg/wms130/request/getmap.go b/pkg/wms130/request/getmap.go index 7cd2725..a8b6dff 100644 --- a/pkg/wms130/request/getmap.go +++ b/pkg/wms130/request/getmap.go @@ -1,427 +1,426 @@ -package request - -import ( - "encoding/xml" - "fmt" - "net/url" - "reflect" - "strconv" - "strings" - - "github.com/pdok/ogc-specifications/pkg/ows" - "github.com/pdok/ogc-specifications/pkg/wms130/capabilities" - "github.com/pdok/ogc-specifications/pkg/wms130/exception" - "gopkg.in/yaml.v2" -) - -// -const ( - getmap = `GetMap` - - // Mandatory - LAYERS = `LAYERS` - STYLES = `STYLES` - CRS = `CRS` - BBOX = `BBOX` - WIDTH = `WIDTH` - HEIGHT = `HEIGHT` - FORMAT = `FORMAT` - - //Optional - TRANSPARENT = `TRANSPARENT` - BGCOLOR = `BGCOLOR` - EXCEPTIONS = `EXCEPTIONS` // defaults to XML - - // TODO: something with Time & Elevation - // TIME = `TIME` - // ELEVATION = `ELEVATION` -) - -// Type returns GetMap -func (gm *GetMap) Type() string { - return getmap -} - -// Validate returns GetMap -func (gm *GetMap) Validate(c capabilities.Capability) ows.Exceptions { - var exceptions ows.Exceptions - - exceptions = append(exceptions, gm.StyledLayerDescriptor.Validate(c)...) - - return exceptions -} - -//GetMapKVP struct -type GetMapKVP struct { - // Table 8 - The Parameters of a GetMap request - Request string `yaml:"request,omitempty"` - Version string `yaml:"version,omitempty"` - Service string `yaml:"service,omitempty"` - Layers string `yaml:"layers,omitempty"` - Styles string `yaml:"styles,omitempty"` - CRS string `yaml:"crs,omitempty"` - Bbox string `yaml:"bbox,omitempty"` - Width string `yaml:"width,omitempty"` - Height string `yaml:"height,omitempty"` - Format string `yaml:"format,omitempty"` - Transparent *string `yaml:"transparent,omitempty"` - BGColor *string `yaml:"bgcolor,omitempty"` - // TODO: something with Time & Elevation - // Time *string `yaml:"time,omitempty"` - // Elevation *string `yaml:"elevation,omitempty"` - Exceptions *string `yaml:"exceptions,omitempty"` -} - -// ParseKVP builds a GetMapKVP object based on the available query parameters -func (gmkvp *GetMapKVP) ParseKVP(query url.Values) ows.Exception { - flatten := map[string]string{} - for k, v := range query { - if len(v) > 1 { - // When there is more then one value - // return a InvalidParameterValue Exception - return ows.InvalidParameterValue(k, strings.Join(v, ",")) - } - flatten[strings.ToLower(k)] = v[0] - } - - y, _ := yaml.Marshal(&flatten) - if err := yaml.Unmarshal(y, &gmkvp); err != nil { - return ows.NoApplicableCode(`Could not read query parameters`) - } - - return nil -} - -// ParseGetMap builds a GetMapKVP object based on a GetMap struct -func (gmkvp *GetMapKVP) ParseGetMap(gm *GetMap) ows.Exception { - gmkvp.Request = getmap - gmkvp.Version = Version - gmkvp.Service = Service - gmkvp.Layers = gm.StyledLayerDescriptor.getLayerKVPValue() - gmkvp.Styles = gm.StyledLayerDescriptor.getStyleKVPValue() - gmkvp.CRS = gm.CRS - gmkvp.Bbox = gm.BoundingBox.BuildKVP() - gmkvp.Width = strconv.Itoa(gm.Output.Size.Width) - gmkvp.Height = strconv.Itoa(gm.Output.Size.Height) - gmkvp.Format = gm.Output.Format - gmkvp.Transparent = gm.Output.Transparent - gmkvp.BGColor = gm.Output.BGcolor - // TODO: something with Time & Elevation - // gmkvp.Time = gm.Time - // gmkvp.Elevation = gm.Elevation - gmkvp.Exceptions = gm.Exceptions - - return nil -} - -// BuildOutput builds a Output struct from the KVP information -func (gmkvp *GetMapKVP) BuildOutput() (Output, ows.Exception) { - output := Output{} - - h, err := strconv.Atoi(gmkvp.Height) - if err != nil { - return output, ows.InvalidParameterValue(HEIGHT, gmkvp.Height) - } - w, err := strconv.Atoi(gmkvp.Width) - if err != nil { - return output, ows.InvalidParameterValue(WIDTH, gmkvp.Width) - } - - output.Size = Size{Height: h, Width: w} - output.Format = gmkvp.Format - output.Transparent = gmkvp.Transparent - output.BGcolor = gmkvp.BGColor - - return output, nil -} - -// BuildStyledLayerDescriptor buils a StyledLayerDescriptor struct from the KVP information -func (gmkvp *GetMapKVP) BuildStyledLayerDescriptor() (StyledLayerDescriptor, ows.Exception) { - var layers, styles []string - if gmkvp.Layers != `` { - layers = strings.Split(gmkvp.Layers, ",") - } - if gmkvp.Styles != `` { - styles = strings.Split(gmkvp.Styles, ",") - } - - sld, err := buildStyledLayerDescriptor(layers, styles) - if err != nil { - return sld, err - } - - return sld, nil -} - -// BuildKVP builds a url.Values query from a GetMapKVP struct -func (gmkvp *GetMapKVP) BuildKVP() url.Values { - query := make(map[string][]string) - - fields := reflect.TypeOf(*gmkvp) - values := reflect.ValueOf(*gmkvp) - - for i := 0; i < fields.NumField(); i++ { - field := fields.Field(i) - value := values.Field(i) - - switch value.Kind() { - case reflect.String: - v := value.String() - query[strings.ToUpper(field.Name)] = []string{v} - case reflect.Ptr: - v := value.Elem() - if v.IsValid() { - query[strings.ToUpper(field.Name)] = []string{fmt.Sprintf("%v", v)} - } - } - } - - return query -} - -// ParseGetMapKVP process the simple struct to a complex struct -func (gm *GetMap) ParseGetMapKVP(gmkvp GetMapKVP) ows.Exception { - gm.BaseRequest.Build(gmkvp.Service, gmkvp.Version) - - sld, err := gmkvp.BuildStyledLayerDescriptor() - if err != nil { - return err - } - gm.StyledLayerDescriptor = sld - - gm.CRS = gmkvp.CRS - - var bbox ows.BoundingBox - if err := bbox.Build(gmkvp.Bbox); err != nil { - return err - } - gm.BoundingBox = bbox - - output, err := gmkvp.BuildOutput() - if err != nil { - return err - } - gm.Output = output - - gm.Exceptions = gmkvp.Exceptions - - return nil -} - -// ParseKVP builds a GetMap object based on the available query parameters -func (gm *GetMap) ParseKVP(query url.Values) ows.Exception { - if len(query) == 0 { - // When there are no query values we know that at least - // the manadorty VERSION parameter is missing. - return ows.MissingParameterValue(VERSION) - } - - gmkvp := GetMapKVP{} - if err := gmkvp.ParseKVP(query); err != nil { - return err - } - - if err := gm.ParseGetMapKVP(gmkvp); err != nil { - return err - } - - return nil -} - -// ParseXML builds a GetMap object based on a XML document -func (gm *GetMap) ParseXML(body []byte) ows.Exception { - var xmlattributes ows.XMLAttribute - if err := xml.Unmarshal(body, &xmlattributes); err != nil { - return ows.MissingParameterValue() - } - xml.Unmarshal(body, &gm) //When object can be Unmarshalled -> XMLAttributes, it can be Unmarshalled -> GetMap - var n []xml.Attr - for _, a := range xmlattributes { - switch strings.ToUpper(a.Name.Local) { - case VERSION: - default: - n = append(n, a) - } - } - gm.BaseRequest.Attr = ows.StripDuplicateAttr(n) - return nil -} - -// BuildKVP builds a new query string that will be proxied -func (gm *GetMap) BuildKVP() url.Values { - gmkvp := GetMapKVP{} - gmkvp.ParseGetMap(gm) - - query := gmkvp.BuildKVP() - // query := map[string][]string{} - - return query -} - -// BuildXML builds a 'new' XML document 'based' on the 'original' XML document -func (gm *GetMap) BuildXML() []byte { - si, _ := xml.MarshalIndent(gm, "", " ") - return append([]byte(xml.Header), si...) -} - -func buildStyledLayerDescriptor(layers, styles []string) (StyledLayerDescriptor, ows.Exception) { - // Because the LAYERS & STYLES parameters are intertwined we process as follows: - // 1. cnt(STYLE) == 0 -> Added LAYERS - // 2. cnt(LAYERS) == 0 -> Added no LAYERS (and no STYLES) - // 3. cnt(LAYERS) == cnt(STYLES) -> merge LAYERS STYLES - // 4. cnt(LAYERS) != cnt(STYLES) -> raise error Style not defined/Styles do not correspond with layers - // normally when 4 would occure this could be done in the validate step... but,.. - // with the serialisation -> struct it would become a valid object (yes!?.. YES!) - // That is because POST xml and GET KVP handle this 'different' (at least not in the same way...) - // When 3 is hit the validation at the Validation step wil resolve this - - // 1. - if len(styles) == 0 { - var sld StyledLayerDescriptor - for _, layer := range layers { - sld.NamedLayer = append(sld.NamedLayer, NamedLayer{Name: layer}) - } - sld.Version = "1.1.0" - return sld, nil - // 2. - } else if len(layers) == 0 { - // do nothing - // will be resolved during validation - - // 3. - } else if len(layers) == len(styles) { - var sld StyledLayerDescriptor - for k, layer := range layers { - sld.NamedLayer = append(sld.NamedLayer, NamedLayer{Name: layer, NamedStyle: &NamedStyle{Name: styles[k]}}) - } - sld.Version = "1.1.0" - return sld, nil - // 4. - } else if len(layers) != len(styles) { - return StyledLayerDescriptor{}, exception.StyleNotDefined() - } - - return StyledLayerDescriptor{}, nil -} - -// TODO maybe 'merge' both func in a single one with 2 outputs -// so their are 'insync' ...? -func (sld *StyledLayerDescriptor) getLayerKVPValue() string { - return strings.Join(sld.GetNamedLayers(), ",") -} - -func (sld *StyledLayerDescriptor) getStyleKVPValue() string { - return strings.Join(sld.GetNamedStyles(), ",") -} - -// GetNamedLayers return an array of the Layer names -func (sld *StyledLayerDescriptor) GetNamedLayers() []string { - - layers := []string{} - for _, l := range sld.NamedLayer { - layers = append(layers, l.Name) - } - - return layers -} - -// GetNamedStyles return an array of the Layer names -func (sld *StyledLayerDescriptor) GetNamedStyles() []string { - - styles := []string{} - for _, l := range sld.NamedLayer { - if l.Name != "" { - if l.NamedStyle != nil { - styles = append(styles, l.NamedStyle.Name) - } else { - styles = append(styles, "") - } - } - } - - return styles -} - -// GetMap struct with the needed parameters/attributes needed for making a GetMap request -// Struct based on http://schemas.opengis.net/sld/1.1//example_getmap.xml -type GetMap struct { - XMLName xml.Name `xml:"GetMap" yaml:"getmap" validate:"required"` - BaseRequest - StyledLayerDescriptor StyledLayerDescriptor `xml:"StyledLayerDescriptor" yaml:"styledlayerdescriptor" validate:"required"` - CRS string `xml:"CRS" yaml:"crs" validate:"required"` - BoundingBox ows.BoundingBox `xml:"BoundingBox" yaml:"boundingbox" validate:"required"` - Output Output `xml:"Output" yaml:"output" validate:"required"` - Exceptions *string `xml:"Exceptions" yaml:"exceptions"` - // TODO: something with Time & Elevation - // Elevation *[]Elevation `xml:"Elevation" yaml:"elavation"` - // Time *string `xml:"Time" yaml:"time"` -} - -// Output struct -type Output struct { - Size Size `xml:"Size" yaml:"size" validate:"required"` - Format string `xml:"Format" yaml:"format" validate:"required"` - Transparent *string `xml:"Transparent" yaml:"transparent"` - BGcolor *string `xml:"BGcolor" yaml:"bgcolor"` -} - -// Size struct -type Size struct { - Width int `xml:"Width" yaml:"width" validate:"required,min=1,max=5000"` - Height int `xml:"Height" yaml:"height" validate:"required,min=1,max=5000"` -} - -// StyledLayerDescriptor struct -type StyledLayerDescriptor struct { - Version string `xml:"version,attr" yaml:"version" validate:"required,eq=1.1.0"` - NamedLayer []NamedLayer `xml:"NamedLayer" yaml:"namedlayer" validate:"required"` -} - -// Validate the StyledLayerDescriptor -func (sld *StyledLayerDescriptor) Validate(capabilities capabilities.Capability) ows.Exceptions { - var unknown []string - for _, l := range sld.GetNamedLayers() { - found := false - for _, c := range capabilities.GetLayerNames() { - if l == c { - found = true - } - } - if !found { - unknown = append(unknown, l) - } - } - - var exceptions ows.Exceptions - if len(unknown) > 0 { - - for _, l := range unknown { - exceptions = append(exceptions, exception.LayerNotDefined(l)) - } - return exceptions - } - - return nil -} - -// NamedLayer struct -type NamedLayer struct { - Name string `xml:"Name" yaml:"name" validate:"required"` - NamedStyle *NamedStyle `xml:"NamedStyle" yaml:"namedstyle"` -} - -// NamedStyle contains the style name that needs be applied -type NamedStyle struct { - Name string `xml:"Name" yaml:"name" validate:"required"` -} - -// Elevation struct for GetMap requests -// The extent string declares what value(s) along the Dimension axis are appropriate for the corresponding layer. -// The extent string has the syntax shown in Table C.2. -type Elevation struct { - Value float64 `xml:"Value" yaml:"value"` - Interval struct { - Min float64 `xml:"Min" yaml:"min"` - Max float64 `xml:"Max" yaml:"max"` - } `xml:"Interval" yaml:"interval"` -} +package request + +import ( + "encoding/xml" + "fmt" + "net/url" + "reflect" + "strconv" + "strings" + + "github.com/pdok/ogc-specifications/pkg/ows" + "github.com/pdok/ogc-specifications/pkg/wms130/capabilities" + "github.com/pdok/ogc-specifications/pkg/wms130/exception" + "gopkg.in/yaml.v2" +) + +// +const ( + getmap = `GetMap` + + // Mandatory + LAYERS = `LAYERS` + STYLES = `STYLES` + CRS = `CRS` + BBOX = `BBOX` + WIDTH = `WIDTH` + HEIGHT = `HEIGHT` + FORMAT = `FORMAT` + + //Optional + TRANSPARENT = `TRANSPARENT` + BGCOLOR = `BGCOLOR` + EXCEPTIONS = `EXCEPTIONS` // defaults to XML + + // TODO: something with Time & Elevation + // TIME = `TIME` + // ELEVATION = `ELEVATION` +) + +// Type returns GetMap +func (gm *GetMap) Type() string { + return getmap +} + +// Validate returns GetMap +func (gm *GetMap) Validate(c capabilities.Capability) ows.Exceptions { + var exceptions ows.Exceptions + + exceptions = append(exceptions, gm.StyledLayerDescriptor.Validate(c)...) + + return exceptions +} + +//GetMapKVP struct +type GetMapKVP struct { + // Table 8 - The Parameters of a GetMap request + Request string `yaml:"request,omitempty"` + Version string `yaml:"version,omitempty"` + Service string `yaml:"service,omitempty"` + Layers string `yaml:"layers,omitempty"` + Styles string `yaml:"styles,omitempty"` + CRS string `yaml:"crs,omitempty"` + Bbox string `yaml:"bbox,omitempty"` + Width string `yaml:"width,omitempty"` + Height string `yaml:"height,omitempty"` + Format string `yaml:"format,omitempty"` + Transparent *string `yaml:"transparent,omitempty"` + BGColor *string `yaml:"bgcolor,omitempty"` + // TODO: something with Time & Elevation + // Time *string `yaml:"time,omitempty"` + // Elevation *string `yaml:"elevation,omitempty"` + Exceptions *string `yaml:"exceptions,omitempty"` +} + +// ParseKVP builds a GetMapKVP object based on the available query parameters +func (gmkvp *GetMapKVP) ParseKVP(query url.Values) ows.Exception { + flatten := map[string]string{} + for k, v := range query { + if len(v) > 1 { + // When there is more then one value + // return a InvalidParameterValue Exception + return ows.InvalidParameterValue(k, strings.Join(v, ",")) + } + flatten[strings.ToLower(k)] = v[0] + } + + y, _ := yaml.Marshal(&flatten) + if err := yaml.Unmarshal(y, &gmkvp); err != nil { + return ows.NoApplicableCode(`Could not read query parameters`) + } + + return nil +} + +// ParseGetMap builds a GetMapKVP object based on a GetMap struct +func (gmkvp *GetMapKVP) ParseGetMap(gm *GetMap) ows.Exception { + gmkvp.Request = getmap + gmkvp.Version = Version + gmkvp.Service = Service + gmkvp.Layers = gm.StyledLayerDescriptor.getLayerKVPValue() + gmkvp.Styles = gm.StyledLayerDescriptor.getStyleKVPValue() + gmkvp.CRS = gm.CRS + gmkvp.Bbox = gm.BoundingBox.BuildKVP() + gmkvp.Width = strconv.Itoa(gm.Output.Size.Width) + gmkvp.Height = strconv.Itoa(gm.Output.Size.Height) + gmkvp.Format = gm.Output.Format + gmkvp.Transparent = gm.Output.Transparent + gmkvp.BGColor = gm.Output.BGcolor + // TODO: something with Time & Elevation + // gmkvp.Time = gm.Time + // gmkvp.Elevation = gm.Elevation + gmkvp.Exceptions = gm.Exceptions + + return nil +} + +// BuildOutput builds a Output struct from the KVP information +func (gmkvp *GetMapKVP) BuildOutput() (Output, ows.Exception) { + output := Output{} + + h, err := strconv.Atoi(gmkvp.Height) + if err != nil { + return output, ows.InvalidParameterValue(HEIGHT, gmkvp.Height) + } + w, err := strconv.Atoi(gmkvp.Width) + if err != nil { + return output, ows.InvalidParameterValue(WIDTH, gmkvp.Width) + } + + output.Size = Size{Height: h, Width: w} + output.Format = gmkvp.Format + output.Transparent = gmkvp.Transparent + output.BGcolor = gmkvp.BGColor + + return output, nil +} + +// BuildStyledLayerDescriptor buils a StyledLayerDescriptor struct from the KVP information +func (gmkvp *GetMapKVP) BuildStyledLayerDescriptor() (StyledLayerDescriptor, ows.Exception) { + var layers, styles []string + if gmkvp.Layers != `` { + layers = strings.Split(gmkvp.Layers, ",") + } + if gmkvp.Styles != `` { + styles = strings.Split(gmkvp.Styles, ",") + } + + sld, err := buildStyledLayerDescriptor(layers, styles) + if err != nil { + return sld, err + } + + return sld, nil +} + +// BuildKVP builds a url.Values query from a GetMapKVP struct +func (gmkvp *GetMapKVP) BuildKVP() url.Values { + query := make(map[string][]string) + + fields := reflect.TypeOf(*gmkvp) + values := reflect.ValueOf(*gmkvp) + + for i := 0; i < fields.NumField(); i++ { + field := fields.Field(i) + value := values.Field(i) + + switch value.Kind() { + case reflect.String: + v := value.String() + query[strings.ToUpper(field.Name)] = []string{v} + case reflect.Ptr: + v := value.Elem() + if v.IsValid() { + query[strings.ToUpper(field.Name)] = []string{fmt.Sprintf("%v", v)} + } + } + } + + return query +} + +// ParseGetMapKVP process the simple struct to a complex struct +func (gm *GetMap) ParseGetMapKVP(gmkvp GetMapKVP) ows.Exception { + gm.BaseRequest.Build(gmkvp.Service, gmkvp.Version) + + sld, err := gmkvp.BuildStyledLayerDescriptor() + if err != nil { + return err + } + gm.StyledLayerDescriptor = sld + + gm.CRS = gmkvp.CRS + + var bbox ows.BoundingBox + if err := bbox.Build(gmkvp.Bbox); err != nil { + return err + } + gm.BoundingBox = bbox + + output, err := gmkvp.BuildOutput() + if err != nil { + return err + } + gm.Output = output + + gm.Exceptions = gmkvp.Exceptions + + return nil +} + +// ParseKVP builds a GetMap object based on the available query parameters +func (gm *GetMap) ParseKVP(query url.Values) ows.Exception { + if len(query) == 0 { + // When there are no query values we know that at least + // the manadorty VERSION parameter is missing. + return ows.MissingParameterValue(VERSION) + } + + gmkvp := GetMapKVP{} + if err := gmkvp.ParseKVP(query); err != nil { + return err + } + + if err := gm.ParseGetMapKVP(gmkvp); err != nil { + return err + } + + return nil +} + +// ParseXML builds a GetMap object based on a XML document +func (gm *GetMap) ParseXML(body []byte) ows.Exception { + var xmlattributes ows.XMLAttribute + if err := xml.Unmarshal(body, &xmlattributes); err != nil { + return ows.MissingParameterValue() + } + xml.Unmarshal(body, &gm) //When object can be Unmarshalled -> XMLAttributes, it can be Unmarshalled -> GetMap + var n []xml.Attr + for _, a := range xmlattributes { + switch strings.ToUpper(a.Name.Local) { + case VERSION: + default: + n = append(n, a) + } + } + gm.BaseRequest.Attr = ows.StripDuplicateAttr(n) + return nil +} + +// BuildKVP builds a new query string that will be proxied +func (gm *GetMap) BuildKVP() url.Values { + gmkvp := GetMapKVP{} + gmkvp.ParseGetMap(gm) + + query := gmkvp.BuildKVP() + // query := map[string][]string{} + + return query +} + +// BuildXML builds a 'new' XML document 'based' on the 'original' XML document +func (gm *GetMap) BuildXML() []byte { + si, _ := xml.MarshalIndent(gm, "", " ") + return append([]byte(xml.Header), si...) +} + +func buildStyledLayerDescriptor(layers, styles []string) (StyledLayerDescriptor, ows.Exception) { + // Because the LAYERS & STYLES parameters are intertwined we process as follows: + // 1. cnt(STYLE) == 0 -> Added LAYERS + // 2. cnt(LAYERS) == 0 -> Added no LAYERS (and no STYLES) + // 3. cnt(LAYERS) == cnt(STYLES) -> merge LAYERS STYLES + // 4. cnt(LAYERS) != cnt(STYLES) -> raise error Style not defined/Styles do not correspond with layers + // normally when 4 would occure this could be done in the validate step... but,.. + // with the serialisation -> struct it would become a valid object (yes!?.. YES!) + // That is because POST xml and GET KVP handle this 'different' (at least not in the same way...) + // When 3 is hit the validation at the Validation step wil resolve this + + // 1. + if len(styles) == 0 { + var sld StyledLayerDescriptor + for _, layer := range layers { + sld.NamedLayer = append(sld.NamedLayer, NamedLayer{Name: layer}) + } + sld.Version = "1.1.0" + return sld, nil + // 2. + } else if len(layers) == 0 { + // do nothing + // will be resolved during validation + + // 3. + } else if len(layers) == len(styles) { + var sld StyledLayerDescriptor + for k, layer := range layers { + sld.NamedLayer = append(sld.NamedLayer, NamedLayer{Name: layer, NamedStyle: &NamedStyle{Name: styles[k]}}) + } + sld.Version = "1.1.0" + return sld, nil + // 4. + } else if len(layers) != len(styles) { + return StyledLayerDescriptor{}, exception.StyleNotDefined() + } + + return StyledLayerDescriptor{}, nil +} + +// TODO maybe 'merge' both func in a single one with 2 outputs +// so their are 'insync' ...? +func (sld *StyledLayerDescriptor) getLayerKVPValue() string { + return strings.Join(sld.GetNamedLayers(), ",") +} + +func (sld *StyledLayerDescriptor) getStyleKVPValue() string { + return strings.Join(sld.GetNamedStyles(), ",") +} + +// GetNamedLayers return an array of the Layer names +func (sld *StyledLayerDescriptor) GetNamedLayers() []string { + + layers := []string{} + for _, l := range sld.NamedLayer { + layers = append(layers, l.Name) + } + + return layers +} + +// GetNamedStyles return an array of the Layer names +func (sld *StyledLayerDescriptor) GetNamedStyles() []string { + styles := []string{} + for _, l := range sld.NamedLayer { + if l.Name != "" { + if l.NamedStyle != nil { + styles = append(styles, l.NamedStyle.Name) + } else { + styles = append(styles, "") + } + } + } + + return styles +} + +// GetMap struct with the needed parameters/attributes needed for making a GetMap request +// Struct based on http://schemas.opengis.net/sld/1.1/example_getmap.xml +type GetMap struct { + XMLName xml.Name `xml:"GetMap" yaml:"getmap" validate:"required"` + BaseRequest + StyledLayerDescriptor StyledLayerDescriptor `xml:"StyledLayerDescriptor" yaml:"styledlayerdescriptor" validate:"required"` + CRS string `xml:"CRS" yaml:"crs" validate:"required"` + BoundingBox ows.BoundingBox `xml:"BoundingBox" yaml:"boundingbox" validate:"required"` + Output Output `xml:"Output" yaml:"output" validate:"required"` + Exceptions *string `xml:"Exceptions" yaml:"exceptions"` + // TODO: something with Time & Elevation + // Elevation *[]Elevation `xml:"Elevation" yaml:"elavation"` + // Time *string `xml:"Time" yaml:"time"` +} + +// Output struct +type Output struct { + Size Size `xml:"Size" yaml:"size" validate:"required"` + Format string `xml:"Format" yaml:"format" validate:"required"` + Transparent *string `xml:"Transparent" yaml:"transparent"` + BGcolor *string `xml:"BGcolor" yaml:"bgcolor"` +} + +// Size struct +type Size struct { + Width int `xml:"Width" yaml:"width" validate:"required,min=1,max=5000"` + Height int `xml:"Height" yaml:"height" validate:"required,min=1,max=5000"` +} + +// StyledLayerDescriptor struct +type StyledLayerDescriptor struct { + Version string `xml:"version,attr" yaml:"version" validate:"required,eq=1.1.0"` + NamedLayer []NamedLayer `xml:"NamedLayer" yaml:"namedlayer" validate:"required"` +} + +// Validate the StyledLayerDescriptor +func (sld *StyledLayerDescriptor) Validate(capabilities capabilities.Capability) ows.Exceptions { + var unknown []string + for _, l := range sld.GetNamedLayers() { + found := false + for _, c := range capabilities.GetLayerNames() { + if l == c { + found = true + } + } + if !found { + unknown = append(unknown, l) + } + } + + var exceptions ows.Exceptions + if len(unknown) > 0 { + + for _, l := range unknown { + exceptions = append(exceptions, exception.LayerNotDefined(l)) + } + return exceptions + } + + return nil +} + +// NamedLayer struct +type NamedLayer struct { + Name string `xml:"Name" yaml:"name" validate:"required"` + NamedStyle *NamedStyle `xml:"NamedStyle" yaml:"namedstyle"` +} + +// NamedStyle contains the style name that needs be applied +type NamedStyle struct { + Name string `xml:"Name" yaml:"name" validate:"required"` +} + +// Elevation struct for GetMap requests +// The extent string declares what value(s) along the Dimension axis are appropriate for the corresponding layer. +// The extent string has the syntax shown in Table C.2. +type Elevation struct { + Value float64 `xml:"Value" yaml:"value"` + Interval struct { + Min float64 `xml:"Min" yaml:"min"` + Max float64 `xml:"Max" yaml:"max"` + } `xml:"Interval" yaml:"interval"` +} diff --git a/pkg/wms130/request/getmap_test.go b/pkg/wms130/request/getmap_test.go index fa4c5b1..bf00a43 100644 --- a/pkg/wms130/request/getmap_test.go +++ b/pkg/wms130/request/getmap_test.go @@ -113,7 +113,7 @@ func TestGetMapParseXML(t *testing.T) { Excepted GetMap Error ows.Exception }{ - // GetMap schemas.opengis.net/sld/1.1.0/example_getmap.xml example request + // GetMap http://schemas.opengis.net/sld/1.1.0/example_getmap.xml example request 0: {Body: []byte(` diff --git a/pkg/wms130/validation/validations.go b/pkg/wms130/validation/validations.go index f24706d..a537034 100644 --- a/pkg/wms130/validation/validations.go +++ b/pkg/wms130/validation/validations.go @@ -13,7 +13,6 @@ func registerValidations(v *validator.Validate) { // BboxValidator implements validator.CustomTypeFunc func BboxValidator(sl validator.StructLevel) { - bbox := sl.Current().Interface().(ows.BoundingBox) if bbox.LowerCorner[0] >= bbox.UpperCorner[0] { @@ -28,7 +27,6 @@ func BboxValidator(sl validator.StructLevel) { // GetMapValidation structlevel func GetMapValidation(sl validator.StructLevel) { - wr := sl.Current().Interface().(GetMapWrapper) // layer diff --git a/pkg/wms130/validation/validator_test.go b/pkg/wms130/validation/validator_test.go index ec795c4..59a133b 100644 --- a/pkg/wms130/validation/validator_test.go +++ b/pkg/wms130/validation/validator_test.go @@ -18,7 +18,6 @@ func sp(s string) *string { } func TestValidation(t *testing.T) { - en := en.New() uni := ut.New(en, en) trans, _ := uni.GetTranslator("en") diff --git a/pkg/wmts100/capabilities/capabilities.go b/pkg/wmts100/capabilities/capabilities.go index 7cc7a1f..54e7ba3 100644 --- a/pkg/wmts100/capabilities/capabilities.go +++ b/pkg/wmts100/capabilities/capabilities.go @@ -27,7 +27,7 @@ func (c Contents) GetTilematrixsets() map[string]bool { return tilematrixsets } -// Layer in struct for repeatablity +// Layer in struct for repeatability type Layer struct { Title string `xml:"ows:Title" yaml:"title"` Abstract string `xml:"ows:Abstract" yaml:"abstract"` @@ -48,19 +48,19 @@ type Layer struct { } `xml:"ResourceURL" yaml:"resourceurl"` } -// TileMatrixSetLink in struct for repeatablity +// TileMatrixSetLink in struct for repeatability type TileMatrixSetLink struct { TileMatrixSet string `xml:"TileMatrixSet" yaml:"tilematrixset"` } -// TileMatrixSet in struct for repeatablity +// TileMatrixSet in struct for repeatability type TileMatrixSet struct { Identifier string `xml:"ows:Identifier" yaml:"identifier"` SupportedCRS string `xml:"ows:SupportedCRS" yaml:"supportedcrs"` TileMatrix []TileMatrix `xml:"TileMatrix" yaml:"tilematrix"` } -// TileMatrix in struct for repeatablity +// TileMatrix in struct for repeatability type TileMatrix struct { Identifier string `xml:"ows:Identifier" yaml:"identifier"` ScaleDenominator string `xml:"ScaleDenominator" yaml:"scaledenominator"` diff --git a/pkg/wmts100/response/getcapabilities.go b/pkg/wmts100/response/getcapabilities.go index 6cd4d20..d8114d2 100644 --- a/pkg/wmts100/response/getcapabilities.go +++ b/pkg/wmts100/response/getcapabilities.go @@ -76,7 +76,7 @@ type ServiceIdentification struct { AccessConstraints string `xml:"ows:AccessConstraints" yaml:"accessconstraints"` } -// ServiceMetadataURL in struct for repeatablity +// ServiceMetadataURL in struct for repeatability type ServiceMetadataURL struct { Href string `xml:"xlink:href,attr" yaml:"href"` } From 2318bc7960f74308d4e7e93be57719434b98d2d5 Mon Sep 17 00:00:00 2001 From: Wouter Date: Thu, 30 Jul 2020 15:18:04 +0200 Subject: [PATCH 19/45] small spelling fixes + sld validation --- pkg/wms130/capabilities/capabilities.go | 61 +++++++++++++++---- pkg/wms130/capabilities/capabilities_test.go | 63 +++++++++++--------- pkg/wms130/exception/exception.go | 8 ++- pkg/wms130/request/getmap.go | 48 ++++++++++----- pkg/wms130/request/getmap_test.go | 10 ++-- 5 files changed, 130 insertions(+), 60 deletions(-) diff --git a/pkg/wms130/capabilities/capabilities.go b/pkg/wms130/capabilities/capabilities.go index aab5488..0ea41a8 100644 --- a/pkg/wms130/capabilities/capabilities.go +++ b/pkg/wms130/capabilities/capabilities.go @@ -53,6 +53,45 @@ type Layer struct { Layer []*Layer `xml:"Layer" yaml:"layer"` } +// StyleDefined checks if the style that is defined is available for the requested layer +func (c *Capability) StyleDefined(layername, stylename string) bool { + defined := false + for _, layer := range c.Layer { + defined = layer.styleDefined(layername, stylename) + if defined { + return defined + } + } + + return defined +} + +// styleDefined checks if the style is defined +func (l *Layer) styleDefined(layername, stylename string) bool { + if *l.Name == layername { + if l.Style != nil { + for _, sld := range l.Style { + if sld.Name == stylename { + return true + } + } + } + } + + // Not found top level, so we go deeper + defined := false + if l.Layer != nil { + for _, nestedlayer := range l.Layer { + defined = nestedlayer.styleDefined(layername, stylename) + if defined { + return defined + } + } + } + + return defined +} + // GetLayerNames returns the available layers as []string func (c *Capability) GetLayerNames() []string { var layers []string @@ -70,7 +109,7 @@ func (c *Capability) GetLayerNames() []string { return layers } -// GetLayerNames returns the available layers as []string +// getLayerNames returns the available layers as []string func (l *Layer) getLayerNames() []string { var layers []string @@ -91,26 +130,26 @@ type RequestType struct { DCPType DCPType `xml:"DCPType" yaml:"dcptype"` } -// Identifier in struct for repeatablity +// Identifier in struct for repeatability type Identifier struct { Authority string `xml:"authority,attr" yaml:"authority"` Value string `xml:",chardata" yaml:"value"` } -// MetadataURL in struct for repeatablity +// MetadataURL in struct for repeatability type MetadataURL struct { Type *string `xml:"type,attr" yaml:"type"` Format *string `xml:"Format" yaml:"format"` OnlineResource OnlineResource `xml:"OnlineResource" yaml:"onlineresource"` } -// AuthorityURL in struct for repeatablity +// AuthorityURL in struct for repeatability type AuthorityURL struct { Name string `xml:"name,attr" yaml:"name"` OnlineResource OnlineResource `xml:"OnlineResource" yaml:"onlineresource"` } -// ExtendedCapabilities containing the inspire extendedcapbilities, when available +// ExtendedCapabilities containing the inspire extendedcapabilities, when available type ExtendedCapabilities struct { MetadataURL struct { Type string `xml:"xsi:type,attr" yaml:"type"` @@ -127,7 +166,7 @@ type ExtendedCapabilities struct { } `xml:"inspire_common:ResponseLanguage" yaml:"responselanguage"` } -// EXGeographicBoundingBox in struct for repeatablity +// EXGeographicBoundingBox in struct for repeatability type EXGeographicBoundingBox struct { WestBoundLongitude float64 `xml:"westBoundLongitude" yaml:"westboundlongitude"` EastBoundLongitude float64 `xml:"eastBoundLongitude" yaml:"eastboundlongitude"` @@ -135,7 +174,7 @@ type EXGeographicBoundingBox struct { NorthBoundLatitude float64 `xml:"northBoundLatitude" yaml:"northboundlatitude"` } -// BoundingBox in struct for repeatablity +// BoundingBox in struct for repeatability type BoundingBox struct { CRS string `xml:"CRS,attr" yaml:"crs"` Minx float64 `xml:"minx,attr" yaml:"minx"` @@ -144,7 +183,7 @@ type BoundingBox struct { Maxy float64 `xml:"maxy,attr" yaml:"maxy"` } -// Style in struct for repeatablity +// Style in struct for repeatability type Style struct { Name string `xml:"Name" yaml:"name"` Title string `xml:"Title" yaml:"title"` @@ -156,7 +195,7 @@ type Style struct { } `xml:"LegendURL" yaml:"legendurl"` } -// DCPType in struct for repeatablity +// DCPType in struct for repeatability type DCPType struct { HTTP struct { Get *Method `xml:"Get" yaml:"get"` @@ -164,12 +203,12 @@ type DCPType struct { } `xml:"HTTP" yaml:"http"` } -// Method in struct for repeatablity +// Method in struct for repeatability type Method struct { OnlineResource OnlineResource `xml:"OnlineResource" yaml:"onlineresource"` } -// OnlineResource in struct for repeatablity +// OnlineResource in struct for repeatability type OnlineResource struct { Xlink *string `xml:"xmlns:xlink,attr" yaml:"xlink"` Type *string `xml:"xlink:type,attr" yaml:"type"` diff --git a/pkg/wms130/capabilities/capabilities_test.go b/pkg/wms130/capabilities/capabilities_test.go index 369b125..ab929f5 100644 --- a/pkg/wms130/capabilities/capabilities_test.go +++ b/pkg/wms130/capabilities/capabilities_test.go @@ -8,39 +8,27 @@ func sp(s string) *string { return &s } -func TestGetLayerNames(t *testing.T) { - capabilities := Capability{ - Layer: []Layer{ - { - Name: sp(`depthOneLayerOne`), - Layer: []*Layer{ - { - Name: sp(`depthTwoLayerThree`), - }, - { - Name: sp(`depthTwoLayerFour`), - Layer: []*Layer{ - { - Name: sp(`depthThreeLayerSix`), - }, - { - Name: sp(`depthThreeLayerSeven`), - }, - }, - }, - }, - }, - { - Name: sp(`depthOneLayerTwo`), - Layer: []*Layer{ - { - Name: sp(`depthTwoLayerFive`), +var capabilities = Capability{ + Layer: []Layer{ + {Name: sp(`depthOneLayerOne`), + Layer: []*Layer{ + {Name: sp(`depthTwoLayerThree`), Style: []*Style{{Name: `StyleOne`}, {Name: `StyleTwo`}}}, + {Name: sp(`depthTwoLayerFour`), + Layer: []*Layer{ + {Name: sp(`depthThreeLayerSix`)}, + {Name: sp(`depthThreeLayerSeven`), Style: []*Style{{Name: `StyleThree`}}}, }, }, }, }, - } + {Name: sp(`depthOneLayerTwo`), + Layer: []*Layer{ + {Name: sp(`depthTwoLayerFive`), Style: []*Style{{Name: `StyleFour`}, {Name: `StyleFive`}}}}, + }, + }, +} +func TestGetLayerNames(t *testing.T) { expected := []string{`depthOneLayerOne`, `depthOneLayerTwo`, `depthTwoLayerThree`, `depthTwoLayerFour`, `depthTwoLayerFive`, `depthThreeLayerSix`, `depthThreeLayerSeven`} for _, n := range capabilities.GetLayerNames() { @@ -55,3 +43,22 @@ func TestGetLayerNames(t *testing.T) { } } } + +func TestStyleDefined(t *testing.T) { + var tests = []struct { + layer string + style string + defined bool + }{ + 0: {layer: `depthOneLayerOne`, style: `none`, defined: false}, + 1: {layer: `depthTwoLayerThree`, style: `StyleTwo`, defined: true}, + 2: {layer: `depthTwoLayerFive`, style: `StyleFour`, defined: true}, + } + + for k, test := range tests { + d := capabilities.StyleDefined(test.layer, test.style) + if test.defined != d { + t.Errorf("test: %d, expected: %t \ngot: %t", k, test.defined, d) + } + } +} diff --git a/pkg/wms130/exception/exception.go b/pkg/wms130/exception/exception.go index b0ea94c..7b099fa 100644 --- a/pkg/wms130/exception/exception.go +++ b/pkg/wms130/exception/exception.go @@ -92,7 +92,13 @@ func LayerNotDefined(s ...string) WMSException { } // StyleNotDefined exception -func StyleNotDefined() WMSException { +func StyleNotDefined(s ...string) WMSException { + if len(s) == 2 { + return WMSException{ + ExceptionText: fmt.Sprintf("The style: %s is not known by the server for the layer: %s", s[0], s[1]), + ExceptionCode: `StyleNotDefined`, + } + } return WMSException{ ExceptionText: `There is a one-to-one correspondence between the values in the LAYERS parameter and the values in the STYLES parameter. Expecting an empty string for the STYLES like STYLES= or comma-separated list STYLES=,,, or using keyword default STYLES=default,default,...`, diff --git a/pkg/wms130/request/getmap.go b/pkg/wms130/request/getmap.go index a8b6dff..c25f107 100644 --- a/pkg/wms130/request/getmap.go +++ b/pkg/wms130/request/getmap.go @@ -135,7 +135,7 @@ func (gmkvp *GetMapKVP) BuildOutput() (Output, ows.Exception) { return output, nil } -// BuildStyledLayerDescriptor buils a StyledLayerDescriptor struct from the KVP information +// BuildStyledLayerDescriptor builds a StyledLayerDescriptor struct from the KVP information func (gmkvp *GetMapKVP) BuildStyledLayerDescriptor() (StyledLayerDescriptor, ows.Exception) { var layers, styles []string if gmkvp.Layers != `` { @@ -270,8 +270,8 @@ func buildStyledLayerDescriptor(layers, styles []string) (StyledLayerDescriptor, // 2. cnt(LAYERS) == 0 -> Added no LAYERS (and no STYLES) // 3. cnt(LAYERS) == cnt(STYLES) -> merge LAYERS STYLES // 4. cnt(LAYERS) != cnt(STYLES) -> raise error Style not defined/Styles do not correspond with layers - // normally when 4 would occure this could be done in the validate step... but,.. - // with the serialisation -> struct it would become a valid object (yes!?.. YES!) + // normally when 4 would occur this could be done in the validate step... but,.. + // with the serialization -> struct it would become a valid object (yes!?.. YES!) // That is because POST xml and GET KVP handle this 'different' (at least not in the same way...) // When 3 is hit the validation at the Validation step wil resolve this @@ -305,7 +305,7 @@ func buildStyledLayerDescriptor(layers, styles []string) (StyledLayerDescriptor, } // TODO maybe 'merge' both func in a single one with 2 outputs -// so their are 'insync' ...? +// so their are 'in sync' ...? func (sld *StyledLayerDescriptor) getLayerKVPValue() string { return strings.Join(sld.GetNamedLayers(), ",") } @@ -316,7 +316,6 @@ func (sld *StyledLayerDescriptor) getStyleKVPValue() string { // GetNamedLayers return an array of the Layer names func (sld *StyledLayerDescriptor) GetNamedLayers() []string { - layers := []string{} for _, l := range sld.NamedLayer { layers = append(layers, l.Name) @@ -352,7 +351,7 @@ type GetMap struct { Output Output `xml:"Output" yaml:"output" validate:"required"` Exceptions *string `xml:"Exceptions" yaml:"exceptions"` // TODO: something with Time & Elevation - // Elevation *[]Elevation `xml:"Elevation" yaml:"elavation"` + // Elevation *[]Elevation `xml:"Elevation" yaml:"elevation"` // Time *string `xml:"Time" yaml:"time"` } @@ -377,26 +376,45 @@ type StyledLayerDescriptor struct { } // Validate the StyledLayerDescriptor -func (sld *StyledLayerDescriptor) Validate(capabilities capabilities.Capability) ows.Exceptions { - var unknown []string - for _, l := range sld.GetNamedLayers() { +func (sld *StyledLayerDescriptor) Validate(c capabilities.Capability) ows.Exceptions { + var unknownLayers []string + var unknownStyles []struct{ layer, style string } + + for _, namedLayer := range sld.NamedLayer { found := false - for _, c := range capabilities.GetLayerNames() { - if l == c { + for _, c := range c.GetLayerNames() { + if namedLayer.Name == c { found = true } } if !found { - unknown = append(unknown, l) + unknownLayers = append(unknownLayers, namedLayer.Name) + } + + if namedLayer.NamedStyle != nil { + if namedLayer.NamedStyle.Name != `` { + if !c.StyleDefined(namedLayer.Name, namedLayer.NamedStyle.Name) { + unknownStyles = append(unknownStyles, struct{ layer, style string }{namedLayer.Name, namedLayer.NamedStyle.Name}) + } + } } } var exceptions ows.Exceptions - if len(unknown) > 0 { - - for _, l := range unknown { + if len(unknownLayers) > 0 { + for _, l := range unknownLayers { exceptions = append(exceptions, exception.LayerNotDefined(l)) } + } + + if len(unknownStyles) > 0 { + for _, sld := range unknownStyles { + exceptions = append(exceptions, exception.StyleNotDefined(sld.style, sld.layer)) + } + + } + + if len(exceptions) > 0 { return exceptions } diff --git a/pkg/wms130/request/getmap_test.go b/pkg/wms130/request/getmap_test.go index bf00a43..1818578 100644 --- a/pkg/wms130/request/getmap_test.go +++ b/pkg/wms130/request/getmap_test.go @@ -72,20 +72,20 @@ func TestValidateStyledLayerDescriptor(t *testing.T) { capabilities: capabilities.Capability{ Layer: []capabilities.Layer{ {Name: sp(`layer1`)}, - {Name: sp(`layer2`)}, + {Name: sp(`layer2`), Style: []*capabilities.Style{{Name: `styleone`}}}, }, }, - sld: StyledLayerDescriptor{NamedLayer: []NamedLayer{{Name: "layer1"}, {Name: "layer2"}}}, + sld: StyledLayerDescriptor{NamedLayer: []NamedLayer{{Name: "layer1", NamedStyle: &NamedStyle{Name: ``}}, {Name: "layer2", NamedStyle: &NamedStyle{Name: `styleone`}}}}, }, 1: { capabilities: capabilities.Capability{ Layer: []capabilities.Layer{ - {Name: sp(`layer2`)}, + {Name: sp(`layer2`), Style: []*capabilities.Style{{Name: `styleone`}}}, {Name: sp(`layer3`)}, }, }, - sld: StyledLayerDescriptor{NamedLayer: []NamedLayer{{Name: "layer1"}, {Name: "layer2"}}}, - exceptions: []ows.Exception{exception.LayerNotDefined(`layer1`)}, + sld: StyledLayerDescriptor{NamedLayer: []NamedLayer{{Name: "layer1"}, {Name: "layer2", NamedStyle: &NamedStyle{Name: `styletwo`}}}}, + exceptions: []ows.Exception{exception.LayerNotDefined(`layer1`), exception.StyleNotDefined(`styletwo`, `layer2`)}, }, } From e37f9c6e6ee60610aeb413a6bd3b2a01dcc2c1c9 Mon Sep 17 00:00:00 2001 From: Wouter Date: Thu, 30 Jul 2020 20:47:39 +0200 Subject: [PATCH 20/45] deleted go-playground validation and move -> own validation rules for more flexibility and readability --- go.mod | 7 +- go.sum | 20 ----- pkg/wms130/capabilities/capabilities.go | 17 +++- pkg/wms130/capabilities/capabilities_test.go | 26 +++--- pkg/wms130/exception/exception.go | 5 +- pkg/wms130/exception/exception_test.go | 3 +- pkg/wms130/request/getmap.go | 48 +++++++++-- pkg/wms130/request/getmap_test.go | 30 ++++--- pkg/wms130/response/getcapabilities.go | 3 +- pkg/wms130/validation/translations.go | 50 ------------ pkg/wms130/validation/validations.go | 44 ---------- pkg/wms130/validation/validator_test.go | 85 -------------------- pkg/wms130/validation/wrappers.go | 12 --- 13 files changed, 99 insertions(+), 251 deletions(-) delete mode 100644 pkg/wms130/validation/translations.go delete mode 100644 pkg/wms130/validation/validations.go delete mode 100644 pkg/wms130/validation/validator_test.go delete mode 100644 pkg/wms130/validation/wrappers.go diff --git a/go.mod b/go.mod index 705c9a9..a025d98 100644 --- a/go.mod +++ b/go.mod @@ -2,9 +2,4 @@ module github.com/pdok/ogc-specifications go 1.14 -require ( - github.com/go-playground/locales v0.13.0 - github.com/go-playground/universal-translator v0.17.0 - github.com/go-playground/validator/v10 v10.3.0 - gopkg.in/yaml.v2 v2.3.0 -) +require gopkg.in/yaml.v2 v2.3.0 diff --git a/go.sum b/go.sum index cac2ae7..168980d 100644 --- a/go.sum +++ b/go.sum @@ -1,24 +1,4 @@ -github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= -github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= -github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= -github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= -github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= -github.com/go-playground/validator/v10 v10.3.0 h1:nZU+7q+yJoFmwvNgv/LnPUkwPal62+b2xXj0AU1Es7o= -github.com/go-playground/validator/v10 v10.3.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= -github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= -github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/pkg/wms130/capabilities/capabilities.go b/pkg/wms130/capabilities/capabilities.go index 0ea41a8..73b5c0a 100644 --- a/pkg/wms130/capabilities/capabilities.go +++ b/pkg/wms130/capabilities/capabilities.go @@ -12,14 +12,27 @@ func (c *Capability) ParseYML(doc []byte) error { return nil } -// Capability base struct +// Capability struct needed for keeping all constraints and capabilities together type Capability struct { + WMSCapabilities + OptionalConstraints +} + +// WMSCapabilities base struct +type WMSCapabilities struct { Request Request `xml:"Request" yaml:"request"` Exception Exception `xml:"Exception" yaml:"exception"` ExtendedCapabilities *ExtendedCapabilities `xml:"inspire_vs:ExtendedCapabilities" yaml:"extendedcapabilities"` Layer []Layer `xml:"Layer" yaml:"layer"` } +// OptionalConstraints struct +type OptionalConstraints struct { + LayerLimit int `xml:"LayerLimit" yaml:"layerlimit"` + MaxWidth int `xml:"MaxWidth" yaml:"maxwidth"` + MaxHeight int `xml:"MaxHeight" yaml:"maxheight"` +} + // Request struct with the different operations, should be filled from the template type Request struct { GetCapabilities RequestType `xml:"GetCapabilities" yaml:"getcapabilities"` @@ -28,6 +41,8 @@ type Request struct { } // Exception struct containing the different available exceptions, should be filled from the template +// default: XML +// other commonly used: BLANK, INIMAGE and JSON type Exception struct { Format []string `xml:"Format" yaml:"format"` } diff --git a/pkg/wms130/capabilities/capabilities_test.go b/pkg/wms130/capabilities/capabilities_test.go index ab929f5..5907c42 100644 --- a/pkg/wms130/capabilities/capabilities_test.go +++ b/pkg/wms130/capabilities/capabilities_test.go @@ -9,21 +9,23 @@ func sp(s string) *string { } var capabilities = Capability{ - Layer: []Layer{ - {Name: sp(`depthOneLayerOne`), - Layer: []*Layer{ - {Name: sp(`depthTwoLayerThree`), Style: []*Style{{Name: `StyleOne`}, {Name: `StyleTwo`}}}, - {Name: sp(`depthTwoLayerFour`), - Layer: []*Layer{ - {Name: sp(`depthThreeLayerSix`)}, - {Name: sp(`depthThreeLayerSeven`), Style: []*Style{{Name: `StyleThree`}}}, + WMSCapabilities: WMSCapabilities{ + Layer: []Layer{ + {Name: sp(`depthOneLayerOne`), + Layer: []*Layer{ + {Name: sp(`depthTwoLayerThree`), Style: []*Style{{Name: `StyleOne`}, {Name: `StyleTwo`}}}, + {Name: sp(`depthTwoLayerFour`), + Layer: []*Layer{ + {Name: sp(`depthThreeLayerSix`)}, + {Name: sp(`depthThreeLayerSeven`), Style: []*Style{{Name: `StyleThree`}}}, + }, }, }, }, - }, - {Name: sp(`depthOneLayerTwo`), - Layer: []*Layer{ - {Name: sp(`depthTwoLayerFive`), Style: []*Style{{Name: `StyleFour`}, {Name: `StyleFive`}}}}, + {Name: sp(`depthOneLayerTwo`), + Layer: []*Layer{ + {Name: sp(`depthTwoLayerFive`), Style: []*Style{{Name: `StyleFour`}, {Name: `StyleFive`}}}}, + }, }, }, } diff --git a/pkg/wms130/exception/exception.go b/pkg/wms130/exception/exception.go index 7b099fa..6093d20 100644 --- a/pkg/wms130/exception/exception.go +++ b/pkg/wms130/exception/exception.go @@ -59,8 +59,9 @@ func (e WMSException) Locator() string { } // InvalidFormat exception -func InvalidFormat() WMSException { +func InvalidFormat(unknownformat string) WMSException { return WMSException{ + ExceptionText: fmt.Sprintf("The format: %s, is a invalid image format", unknownformat), ExceptionCode: `InvalidFormat`, } } @@ -121,7 +122,7 @@ func LayerNotQueryable(s ...string) WMSException { } // InvalidPoint exception -// i and j are strings so we can none int values in the exception +// i and j are strings so we can return none integer values in the exception func InvalidPoint(i, j string) WMSException { // TODO provide giving WIDTH and HEIGHT values in Exception response return WMSException{ diff --git a/pkg/wms130/exception/exception_test.go b/pkg/wms130/exception/exception_test.go index b665baf..7e41479 100644 --- a/pkg/wms130/exception/exception_test.go +++ b/pkg/wms130/exception/exception_test.go @@ -18,7 +18,8 @@ func TestWFSException(t *testing.T) { exceptionCode: "", locatorCode: "", }, - 1: {exception: InvalidFormat(), + 1: {exception: InvalidFormat(`unknownimage`), + exceptionText: "The format: unknownimage, is a invalid image format", exceptionCode: "InvalidFormat", }, 2: {exception: InvalidCRS(), diff --git a/pkg/wms130/request/getmap.go b/pkg/wms130/request/getmap.go index c25f107..4527167 100644 --- a/pkg/wms130/request/getmap.go +++ b/pkg/wms130/request/getmap.go @@ -47,6 +47,7 @@ func (gm *GetMap) Validate(c capabilities.Capability) ows.Exceptions { var exceptions ows.Exceptions exceptions = append(exceptions, gm.StyledLayerDescriptor.Validate(c)...) + exceptions = append(exceptions, gm.Output.Validate(c)...) return exceptions } @@ -104,11 +105,21 @@ func (gmkvp *GetMapKVP) ParseGetMap(gm *GetMap) ows.Exception { gmkvp.Width = strconv.Itoa(gm.Output.Size.Width) gmkvp.Height = strconv.Itoa(gm.Output.Size.Height) gmkvp.Format = gm.Output.Format - gmkvp.Transparent = gm.Output.Transparent - gmkvp.BGColor = gm.Output.BGcolor + + if gm.Output.Transparent != nil { + t := *gm.Output.Transparent + tp := strconv.FormatBool(t) + gmkvp.Transparent = &tp + } + + if gm.Output.BGcolor != nil { + gmkvp.BGColor = gm.Output.BGcolor + } + // TODO: something with Time & Elevation // gmkvp.Time = gm.Time // gmkvp.Elevation = gm.Elevation + gmkvp.Exceptions = gm.Exceptions return nil @@ -129,7 +140,9 @@ func (gmkvp *GetMapKVP) BuildOutput() (Output, ows.Exception) { output.Size = Size{Height: h, Width: w} output.Format = gmkvp.Format - output.Transparent = gmkvp.Transparent + if b, err := strconv.ParseBool(*gmkvp.Transparent); err == nil { + output.Transparent = &b + } output.BGcolor = gmkvp.BGColor return output, nil @@ -355,11 +368,37 @@ type GetMap struct { // Time *string `xml:"Time" yaml:"time"` } +// Validate validates the output parameters +func (output *Output) Validate(c capabilities.Capability) ows.Exceptions { + var exceptions ows.Exceptions + if output.Size.Width > c.MaxWidth { + exceptions = append(exceptions, ows.NoApplicableCode(fmt.Sprintf("Image size out of range, WIDTH must be between 1 and %d pixels", c.MaxWidth))) + } + if output.Size.Height > c.MaxHeight { + exceptions = append(exceptions, ows.NoApplicableCode(fmt.Sprintf("Image size out of range, HEIGHT must be between 1 and %d pixels", c.MaxHeight))) + } + + for _, format := range c.WMSCapabilities.Request.GetMap.Format { + found := false + if format == output.Format { + found = true + } + if !found { + exceptions = append(exceptions, exception.InvalidFormat(output.Format)) + } + } + + // Transparent is a bool so when it is parsed around in the application it is already valid + // TODO: BGColor -> https://stackoverflow.com/questions/54197913/parse-hex-string-to-image-color + + return nil +} + // Output struct type Output struct { Size Size `xml:"Size" yaml:"size" validate:"required"` Format string `xml:"Format" yaml:"format" validate:"required"` - Transparent *string `xml:"Transparent" yaml:"transparent"` + Transparent *bool `xml:"Transparent" yaml:"transparent"` BGcolor *string `xml:"BGcolor" yaml:"bgcolor"` } @@ -411,7 +450,6 @@ func (sld *StyledLayerDescriptor) Validate(c capabilities.Capability) ows.Except for _, sld := range unknownStyles { exceptions = append(exceptions, exception.StyleNotDefined(sld.style, sld.layer)) } - } if len(exceptions) > 0 { diff --git a/pkg/wms130/request/getmap_test.go b/pkg/wms130/request/getmap_test.go index 1818578..21ef8e1 100644 --- a/pkg/wms130/request/getmap_test.go +++ b/pkg/wms130/request/getmap_test.go @@ -18,6 +18,10 @@ func ip(i int) *int { return &i } +func bp(b bool) *bool { + return &b +} + func TestGetMapType(t *testing.T) { dft := GetMap{} if dft.Type() != `GetMap` { @@ -70,18 +74,22 @@ func TestValidateStyledLayerDescriptor(t *testing.T) { }{ 0: { capabilities: capabilities.Capability{ - Layer: []capabilities.Layer{ - {Name: sp(`layer1`)}, - {Name: sp(`layer2`), Style: []*capabilities.Style{{Name: `styleone`}}}, + WMSCapabilities: capabilities.WMSCapabilities{ + Layer: []capabilities.Layer{ + {Name: sp(`layer1`)}, + {Name: sp(`layer2`), Style: []*capabilities.Style{{Name: `styleone`}}}, + }, }, }, sld: StyledLayerDescriptor{NamedLayer: []NamedLayer{{Name: "layer1", NamedStyle: &NamedStyle{Name: ``}}, {Name: "layer2", NamedStyle: &NamedStyle{Name: `styleone`}}}}, }, 1: { capabilities: capabilities.Capability{ - Layer: []capabilities.Layer{ - {Name: sp(`layer2`), Style: []*capabilities.Style{{Name: `styleone`}}}, - {Name: sp(`layer3`)}, + WMSCapabilities: capabilities.WMSCapabilities{ + Layer: []capabilities.Layer{ + {Name: sp(`layer2`), Style: []*capabilities.Style{{Name: `styleone`}}}, + {Name: sp(`layer3`)}, + }, }, }, sld: StyledLayerDescriptor{NamedLayer: []NamedLayer{{Name: "layer1"}, {Name: "layer2", NamedStyle: &NamedStyle{Name: `styletwo`}}}}, @@ -180,7 +188,7 @@ func TestGetMapParseXML(t *testing.T) { Output: Output{ Size: Size{Width: 1024, Height: 512}, Format: "image/jpeg", - Transparent: sp("false")}, + Transparent: bp(false)}, Exceptions: sp("XML"), }, }, @@ -303,7 +311,7 @@ func TestGetMapParseKVP(t *testing.T) { Output: Output{ Size: Size{Width: 1024, Height: 512}, Format: "image/jpeg", - Transparent: sp("false"), + Transparent: bp(false), BGcolor: sp(`0x7F7F7F`)}, Exceptions: sp("XML"), }}, @@ -347,7 +355,7 @@ func TestGetMapBuildKVP(t *testing.T) { Output: Output{ Size: Size{Width: 1024, Height: 512}, Format: "image/jpeg", - Transparent: sp("false")}, + Transparent: bp(false)}, Exceptions: sp("XML"), }, Excepted: map[string][]string{ LAYERS: {`Rivers,Roads,Houses`}, @@ -535,7 +543,7 @@ func BenchmarkGetMapBuildKVP(b *testing.B) { Output: Output{ Size: Size{Width: 1024, Height: 512}, Format: "image/jpeg", - Transparent: sp("false")}, + Transparent: bp(false)}, Exceptions: sp("XML"), } for i := 0; i < b.N; i++ { @@ -573,7 +581,7 @@ func BenchmarkGetMapBuildXML(b *testing.B) { Output: Output{ Size: Size{Width: 1024, Height: 512}, Format: "image/jpeg", - Transparent: sp("false")}, + Transparent: bp(false)}, Exceptions: sp("XML"), } for i := 0; i < b.N; i++ { diff --git a/pkg/wms130/response/getcapabilities.go b/pkg/wms130/response/getcapabilities.go index 3bf07af..3a6159d 100644 --- a/pkg/wms130/response/getcapabilities.go +++ b/pkg/wms130/response/getcapabilities.go @@ -99,6 +99,5 @@ type WMSService struct { } `xml:"ContactInformation"` Fees string `xml:"Fees" yaml:"fees"` AccessConstraints string `xml:"AccessConstraints" yaml:"accessconstraints"` - MaxWidth int `xml:"MaxWidth" yaml:"maxwidth"` - MaxHeight int `xml:"MaxHeight" yaml:"maxheight"` + capabilities.OptionalConstraints } diff --git a/pkg/wms130/validation/translations.go b/pkg/wms130/validation/translations.go deleted file mode 100644 index 99d34da..0000000 --- a/pkg/wms130/validation/translations.go +++ /dev/null @@ -1,50 +0,0 @@ -package validation - -import ( - "fmt" - - ut "github.com/go-playground/universal-translator" - "github.com/go-playground/validator/v10" -) - -func registerTranslations(v *validator.Validate, trans *ut.Translator) { - - v.RegisterTranslation( - "epsg", - *trans, - func(ut ut.Translator) error { - return ut.Add("epsg", "{0} has a invalid value: {1}", true) - }, - func(ut ut.Translator, fe validator.FieldError) string { - t, _ := ut.T("epsg", fe.Field(), fmt.Sprintf("%v", fe.Value())) - return t - }, - ) - - v.RegisterTranslation("version", *trans, - func(ut ut.Translator) error { - return ut.Add("version", "{0} has a invalid value: {1}", true) - }, - func(ut ut.Translator, fe validator.FieldError) string { - t, _ := ut.T("version", fe.Field(), fmt.Sprintf("%v", fe.Value())) - return t - }) - - v.RegisterTranslation("required", *trans, - func(ut ut.Translator) error { - return ut.Add("required", "{0} is missing", true) - }, - func(ut ut.Translator, fe validator.FieldError) string { - t, _ := ut.T("required", fe.Field()) - return t - }) - - v.RegisterTranslation("knownlayer", *trans, - func(ut ut.Translator) error { - return ut.Add("knownlayer", "{0} has a invalid value {1}", true) - }, - func(ut ut.Translator, fe validator.FieldError) string { - t, _ := ut.T("knownlayer", fe.Field(), fmt.Sprintf("%v", fe.Value())) - return t - }) -} diff --git a/pkg/wms130/validation/validations.go b/pkg/wms130/validation/validations.go deleted file mode 100644 index a537034..0000000 --- a/pkg/wms130/validation/validations.go +++ /dev/null @@ -1,44 +0,0 @@ -package validation - -import ( - "github.com/go-playground/validator/v10" - ows "github.com/pdok/ogc-specifications/pkg/ows" -) - -func registerValidations(v *validator.Validate) { - - v.RegisterStructValidation(BboxValidator, ows.BoundingBox{}) - v.RegisterStructValidation(GetMapValidation, GetMapWrapper{}) -} - -// BboxValidator implements validator.CustomTypeFunc -func BboxValidator(sl validator.StructLevel) { - bbox := sl.Current().Interface().(ows.BoundingBox) - - if bbox.LowerCorner[0] >= bbox.UpperCorner[0] { - sl.ReportError(bbox, "lowerCorner", "LowerCorner", "bbox", `bbox`) - } - - if bbox.LowerCorner[1] >= bbox.UpperCorner[1] { - sl.ReportError(bbox, "lowerCorner", "LowerCorner", "bbox", `bbox`) - } - -} - -// GetMapValidation structlevel -func GetMapValidation(sl validator.StructLevel) { - wr := sl.Current().Interface().(GetMapWrapper) - - // layer - for _, sldname := range wr.getmap.StyledLayerDescriptor.GetNamedLayers() { - known := false - for _, l := range wr.capabilities.Layer { - if *l.Name == sldname { - known = true - } - } - if !known { - sl.ReportError(wr.getmap.StyledLayerDescriptor.NamedLayer, "namedlayer", "NamedLayer", "knownlayer", sldname) - } - } -} diff --git a/pkg/wms130/validation/validator_test.go b/pkg/wms130/validation/validator_test.go deleted file mode 100644 index 59a133b..0000000 --- a/pkg/wms130/validation/validator_test.go +++ /dev/null @@ -1,85 +0,0 @@ -package validation - -import ( - "encoding/xml" - "testing" - - "github.com/go-playground/locales/en" - ut "github.com/go-playground/universal-translator" - "github.com/go-playground/validator/v10" - en_translations "github.com/go-playground/validator/v10/translations/en" - "github.com/pdok/ogc-specifications/pkg/ows" - "github.com/pdok/ogc-specifications/pkg/wms130/capabilities" - "github.com/pdok/ogc-specifications/pkg/wms130/request" -) - -func sp(s string) *string { - return &s -} - -func TestValidation(t *testing.T) { - en := en.New() - uni := ut.New(en, en) - trans, _ := uni.GetTranslator("en") - - v := validator.New() - en_translations.RegisterDefaultTranslations(v, trans) - - registerValidations(v) - registerTranslations(v, &trans) - - capabilities := capabilities.Capability{ - Layer: []capabilities.Layer{ - {Name: sp(`Rivers`), CRS: []*string{sp(`EPSG:4326`)}}, - {Name: sp(`Roads`), CRS: []*string{sp(`EPSG:4326`)}}, - {Name: sp(`Houses`), CRS: []*string{sp(`EPSG:4326`)}}, - }, - } - - var tests = []struct { - Object request.GetMap - }{ - 0: { - Object: request.GetMap{ - XMLName: xml.Name{Local: "GetMap"}, - BaseRequest: request.BaseRequest{ - Version: "1.3.0", - Service: "WMS", - }, - StyledLayerDescriptor: request.StyledLayerDescriptor{ - Version: "1.1.0", - NamedLayer: []request.NamedLayer{ - {Name: "Rivers", NamedStyle: &request.NamedStyle{Name: "CenterLine"}}, - {Name: "Roads", NamedStyle: &request.NamedStyle{Name: "CenterLine"}}, - {Name: "Houses", NamedStyle: &request.NamedStyle{Name: "Outline"}}, - }}, - CRS: "EPSG:4326", - BoundingBox: ows.BoundingBox{ - LowerCorner: [2]float64{-180.0, -90.0}, - UpperCorner: [2]float64{180.0, 90.0}, - }, - Output: request.Output{ - Size: request.Size{Width: 1024, Height: 512}, - Format: "image/jpeg", - Transparent: sp("false")}, - Exceptions: sp("XML"), - }, - }, - } - - for k, n := range tests { - - wr := GetMapWrapper{capabilities: &capabilities, getmap: &n.Object} - - err := v.Struct(wr) - if err != nil { - t.Errorf("test: %d, got: %s", k, (err.(validator.ValidationErrors)).Translate(trans)) - } - - err = v.Struct(n.Object) - if err != nil { - - t.Errorf("test: %d, got: %s", k, (err.(validator.ValidationErrors)).Translate(trans)) - } - } -} diff --git a/pkg/wms130/validation/wrappers.go b/pkg/wms130/validation/wrappers.go deleted file mode 100644 index 77ed0c5..0000000 --- a/pkg/wms130/validation/wrappers.go +++ /dev/null @@ -1,12 +0,0 @@ -package validation - -import ( - "github.com/pdok/ogc-specifications/pkg/wms130/capabilities" - "github.com/pdok/ogc-specifications/pkg/wms130/request" -) - -// GetMapWrapper struct -type GetMapWrapper struct { - capabilities *capabilities.Capability - getmap *request.GetMap -} From 75f3a9b99b2be2db499ac49ec4bbf47d7b4105af Mon Sep 17 00:00:00 2001 From: Wouter Date: Thu, 30 Jul 2020 20:51:56 +0200 Subject: [PATCH 21/45] added TODO --- pkg/wfs200/response/describefeaturetype.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/wfs200/response/describefeaturetype.go b/pkg/wfs200/response/describefeaturetype.go index bfd8be3..174ffa7 100644 --- a/pkg/wfs200/response/describefeaturetype.go +++ b/pkg/wfs200/response/describefeaturetype.go @@ -2,6 +2,8 @@ package response import "encoding/xml" +// Schema struct +// TODO type Schema struct { XMLName xml.Name `xml:"schema"` Text string `xml:",chardata"` From 4c13385797aaa39bb7e4ccb69b51e832d12d3929 Mon Sep 17 00:00:00 2001 From: Wouter Date: Tue, 4 Aug 2020 20:31:30 +0200 Subject: [PATCH 22/45] spelling & init drawio image --- README.md | 12 ++++---- images/layout.drawio | 69 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 6 deletions(-) create mode 100644 images/layout.drawio diff --git a/README.md b/README.md index 7f35820..8b8dc93 100644 --- a/README.md +++ b/README.md @@ -4,10 +4,10 @@ [![GitHub release](https://img.shields.io/github/release/PDOK/ogc-specifications.svg)](https://github.com/PDOK/ogc-specifications/releases) [![Go Report Card](https://goreportcard.com/badge/PDOK/ogc-specifications)](https://goreportcard.com/report/PDOK/ogc-specifications) -The package ogc-specifications is a implementation of the OGC Webservice Specifications as defined by the [OGC](https://www.ogc.org/). -This package has support for the following OGC Webservice Specifications: +The package ogc-specifications is a implementation of the OGC Web Service Specifications as defined by the [OGC](https://www.ogc.org/). +This package has support for the following OGC Web Service Specifications: -| Spec | Version | Operation | Request | Reponse | +| Spec | Version | Operation | Request | Response | | --- | --- | --- | --- | --- | | WMS | 1.3.0 | GetCapabilities | :heavy_check_mark: | :grey_exclamation: | | WMS | 1.3.0 | GetMap | :heavy_check_mark: | | @@ -18,11 +18,11 @@ This package has support for the following OGC Webservice Specifications: | WMTS | 1.0.0 | GetCapabilities | :heavy_check_mark: | :grey_exclamation: | | WCS | 2.0.1 | GetCapabilities | :heavy_check_mark: | :grey_exclamation: | -It will provide the user with structs that can be used with in a developers application, so one doesn't needs to create/build those complex structs for 'every' application that has more then 'simple' interaction with a OGC Webservice. It will allow the developer to parse XML documents and query strings like they are defined in the OGC specification an build go structs with it and it will generate XML documents and query strings based on those structs. +It will provide the user with OperationRequest, OperationResponse and Capabilities structs that can be used with in a developers application, so one doesn't needs to create/build those complex structs for 'every' application that has more then 'simple' interaction with a OGC Web Service. It will allow the developer to parse XML documents and query strings like they are defined in the OGC specification an build go structs with it and it will generate XML documents and KVP query strings based on those structs. ## Notice -This is still a 'work in progres' with the following major todo's: +This is still a 'work-in-progress' with the following major to do's: - [ ] Validation support - [ ] YAML parser @@ -60,7 +60,7 @@ And generate CPU & MEM profiles: go test -cpuprofile cpu.prof -memprofile mem.prof -bench=. ./... ``` -For visualisation use [pprof](https://github.com/google/pprof) +For visualization use [pprof](https://github.com/google/pprof) ## Usage diff --git a/images/layout.drawio b/images/layout.drawio new file mode 100644 index 0000000..5901d83 --- /dev/null +++ b/images/layout.drawio @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From edce71d8fee5d27a9fa93bee0716c8a10ab1e0e9 Mon Sep 17 00:00:00 2001 From: Wouter Date: Tue, 4 Aug 2020 20:32:20 +0200 Subject: [PATCH 23/45] OperationRequestKVP as seperated interface, init for GetMap --- images/layout.drawio | 136 +++++++++++----------- pkg/ows/operationkvprequest.go | 12 ++ pkg/wms130/request/getmap.go | 143 ----------------------- pkg/wms130/request/getmap_test.go | 4 +- pkg/wms130/request/getmapkvp.go | 185 ++++++++++++++++++++++++++++++ 5 files changed, 267 insertions(+), 213 deletions(-) create mode 100644 pkg/ows/operationkvprequest.go create mode 100644 pkg/wms130/request/getmapkvp.go diff --git a/images/layout.drawio b/images/layout.drawio index 5901d83..4d3822a 100644 --- a/images/layout.drawio +++ b/images/layout.drawio @@ -1,69 +1,69 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/pkg/ows/operationkvprequest.go b/pkg/ows/operationkvprequest.go new file mode 100644 index 0000000..2592a09 --- /dev/null +++ b/pkg/ows/operationkvprequest.go @@ -0,0 +1,12 @@ +package ows + +import "net/url" + +// OperationKVPRequest interface +type OperationKVPRequest interface { + Type() string + Validate(Capability) Exceptions + + ParseKVP(url.Values) Exception + BuildKVP() url.Values +} diff --git a/pkg/wms130/request/getmap.go b/pkg/wms130/request/getmap.go index 4527167..f6be548 100644 --- a/pkg/wms130/request/getmap.go +++ b/pkg/wms130/request/getmap.go @@ -4,14 +4,11 @@ import ( "encoding/xml" "fmt" "net/url" - "reflect" - "strconv" "strings" "github.com/pdok/ogc-specifications/pkg/ows" "github.com/pdok/ogc-specifications/pkg/wms130/capabilities" "github.com/pdok/ogc-specifications/pkg/wms130/exception" - "gopkg.in/yaml.v2" ) // @@ -52,146 +49,6 @@ func (gm *GetMap) Validate(c capabilities.Capability) ows.Exceptions { return exceptions } -//GetMapKVP struct -type GetMapKVP struct { - // Table 8 - The Parameters of a GetMap request - Request string `yaml:"request,omitempty"` - Version string `yaml:"version,omitempty"` - Service string `yaml:"service,omitempty"` - Layers string `yaml:"layers,omitempty"` - Styles string `yaml:"styles,omitempty"` - CRS string `yaml:"crs,omitempty"` - Bbox string `yaml:"bbox,omitempty"` - Width string `yaml:"width,omitempty"` - Height string `yaml:"height,omitempty"` - Format string `yaml:"format,omitempty"` - Transparent *string `yaml:"transparent,omitempty"` - BGColor *string `yaml:"bgcolor,omitempty"` - // TODO: something with Time & Elevation - // Time *string `yaml:"time,omitempty"` - // Elevation *string `yaml:"elevation,omitempty"` - Exceptions *string `yaml:"exceptions,omitempty"` -} - -// ParseKVP builds a GetMapKVP object based on the available query parameters -func (gmkvp *GetMapKVP) ParseKVP(query url.Values) ows.Exception { - flatten := map[string]string{} - for k, v := range query { - if len(v) > 1 { - // When there is more then one value - // return a InvalidParameterValue Exception - return ows.InvalidParameterValue(k, strings.Join(v, ",")) - } - flatten[strings.ToLower(k)] = v[0] - } - - y, _ := yaml.Marshal(&flatten) - if err := yaml.Unmarshal(y, &gmkvp); err != nil { - return ows.NoApplicableCode(`Could not read query parameters`) - } - - return nil -} - -// ParseGetMap builds a GetMapKVP object based on a GetMap struct -func (gmkvp *GetMapKVP) ParseGetMap(gm *GetMap) ows.Exception { - gmkvp.Request = getmap - gmkvp.Version = Version - gmkvp.Service = Service - gmkvp.Layers = gm.StyledLayerDescriptor.getLayerKVPValue() - gmkvp.Styles = gm.StyledLayerDescriptor.getStyleKVPValue() - gmkvp.CRS = gm.CRS - gmkvp.Bbox = gm.BoundingBox.BuildKVP() - gmkvp.Width = strconv.Itoa(gm.Output.Size.Width) - gmkvp.Height = strconv.Itoa(gm.Output.Size.Height) - gmkvp.Format = gm.Output.Format - - if gm.Output.Transparent != nil { - t := *gm.Output.Transparent - tp := strconv.FormatBool(t) - gmkvp.Transparent = &tp - } - - if gm.Output.BGcolor != nil { - gmkvp.BGColor = gm.Output.BGcolor - } - - // TODO: something with Time & Elevation - // gmkvp.Time = gm.Time - // gmkvp.Elevation = gm.Elevation - - gmkvp.Exceptions = gm.Exceptions - - return nil -} - -// BuildOutput builds a Output struct from the KVP information -func (gmkvp *GetMapKVP) BuildOutput() (Output, ows.Exception) { - output := Output{} - - h, err := strconv.Atoi(gmkvp.Height) - if err != nil { - return output, ows.InvalidParameterValue(HEIGHT, gmkvp.Height) - } - w, err := strconv.Atoi(gmkvp.Width) - if err != nil { - return output, ows.InvalidParameterValue(WIDTH, gmkvp.Width) - } - - output.Size = Size{Height: h, Width: w} - output.Format = gmkvp.Format - if b, err := strconv.ParseBool(*gmkvp.Transparent); err == nil { - output.Transparent = &b - } - output.BGcolor = gmkvp.BGColor - - return output, nil -} - -// BuildStyledLayerDescriptor builds a StyledLayerDescriptor struct from the KVP information -func (gmkvp *GetMapKVP) BuildStyledLayerDescriptor() (StyledLayerDescriptor, ows.Exception) { - var layers, styles []string - if gmkvp.Layers != `` { - layers = strings.Split(gmkvp.Layers, ",") - } - if gmkvp.Styles != `` { - styles = strings.Split(gmkvp.Styles, ",") - } - - sld, err := buildStyledLayerDescriptor(layers, styles) - if err != nil { - return sld, err - } - - return sld, nil -} - -// BuildKVP builds a url.Values query from a GetMapKVP struct -func (gmkvp *GetMapKVP) BuildKVP() url.Values { - query := make(map[string][]string) - - fields := reflect.TypeOf(*gmkvp) - values := reflect.ValueOf(*gmkvp) - - for i := 0; i < fields.NumField(); i++ { - field := fields.Field(i) - value := values.Field(i) - - switch value.Kind() { - case reflect.String: - v := value.String() - query[strings.ToUpper(field.Name)] = []string{v} - case reflect.Ptr: - v := value.Elem() - if v.IsValid() { - query[strings.ToUpper(field.Name)] = []string{fmt.Sprintf("%v", v)} - } - } - } - - return query -} - // ParseGetMapKVP process the simple struct to a complex struct func (gm *GetMap) ParseGetMapKVP(gmkvp GetMapKVP) ows.Exception { gm.BaseRequest.Build(gmkvp.Service, gmkvp.Version) diff --git a/pkg/wms130/request/getmap_test.go b/pkg/wms130/request/getmap_test.go index 21ef8e1..37eed0d 100644 --- a/pkg/wms130/request/getmap_test.go +++ b/pkg/wms130/request/getmap_test.go @@ -358,6 +358,7 @@ func TestGetMapBuildKVP(t *testing.T) { Transparent: bp(false)}, Exceptions: sp("XML"), }, Excepted: map[string][]string{ + VERSION: {Version}, LAYERS: {`Rivers,Roads,Houses`}, STYLES: {`CenterLine,CenterLine,Outline`}, CRS: {`EPSG:4326`}, @@ -367,7 +368,6 @@ func TestGetMapBuildKVP(t *testing.T) { HEIGHT: {`512`}, WIDTH: {`1024`}, TRANSPARENT: {`false`}, - VERSION: {`1.3.0`}, REQUEST: {`GetMap`}, SERVICE: {`WMS`}, }}, @@ -387,7 +387,7 @@ func TestGetMapBuildKVP(t *testing.T) { FORMAT: {``}, HEIGHT: {`0`}, WIDTH: {`0`}, - VERSION: {`1.3.0`}, + VERSION: {Version}, REQUEST: {`GetMap`}, SERVICE: {`WMS`}, EXCEPTIONS: {`XML`}, diff --git a/pkg/wms130/request/getmapkvp.go b/pkg/wms130/request/getmapkvp.go new file mode 100644 index 0000000..f81c85c --- /dev/null +++ b/pkg/wms130/request/getmapkvp.go @@ -0,0 +1,185 @@ +package request + +import ( + "net/url" + "strconv" + "strings" + + "github.com/pdok/ogc-specifications/pkg/ows" +) + +//GetMapKVP struct +type GetMapKVP struct { + // Table 8 - The Parameters of a GetMap request + Service string `yaml:"service,omitempty"` + GetMapKVPMandatory + GetMapKVPOptional +} + +// GetMapKVPMandatory struct containing the mandatory WMS request KVP +type GetMapKVPMandatory struct { + Version string `yaml:"version,omitempty"` + Request string `yaml:"request,omitempty"` + Layers string `yaml:"layers,omitempty"` + Styles string `yaml:"styles,omitempty"` + CRS string `yaml:"crs,omitempty"` + Bbox string `yaml:"bbox,omitempty"` + Width string `yaml:"width,omitempty"` + Height string `yaml:"height,omitempty"` + Format string `yaml:"format,omitempty"` +} + +// GetMapKVPOptional struct containing the optional WMS request KVP +type GetMapKVPOptional struct { + Transparent *string `yaml:"transparent,omitempty"` + BGColor *string `yaml:"bgcolor,omitempty"` + Exceptions *string `yaml:"exceptions,omitempty"` + // TODO: something with Time & Elevation + // Time *string `yaml:"time,omitempty"` + // Elevation *string `yaml:"elevation,omitempty"` +} + +// ParseKVP builds a GetMapKVP object based on the available query parameters +func (gmkvp *GetMapKVP) ParseKVP(query url.Values) ows.Exception { + var exceptions ows.Exceptions + for k, v := range query { + if len(v) != 1 { + exceptions = append(exceptions, ows.InvalidParameterValue(k, strings.Join(v, ","))) + } else { + switch k { + case SERVICE: + gmkvp.Service = v[0] + case VERSION: + gmkvp.GetMapKVPMandatory.Version = v[0] + case REQUEST: + gmkvp.GetMapKVPMandatory.Request = v[0] + case LAYERS: + gmkvp.GetMapKVPMandatory.Layers = v[0] + case STYLES: + gmkvp.GetMapKVPMandatory.Styles = v[0] + case CRS: + gmkvp.GetMapKVPMandatory.CRS = v[0] + case BBOX: + gmkvp.GetMapKVPMandatory.Bbox = v[0] + case WIDTH: + gmkvp.GetMapKVPMandatory.Width = v[0] + case HEIGHT: + gmkvp.GetMapKVPMandatory.Height = v[0] + case FORMAT: + gmkvp.GetMapKVPMandatory.Format = v[0] + case TRANSPARENT: + vp := v[0] + gmkvp.GetMapKVPOptional.Transparent = &vp + case BGCOLOR: + vp := v[0] + gmkvp.GetMapKVPOptional.BGColor = &vp + case EXCEPTIONS: + vp := v[0] + gmkvp.GetMapKVPOptional.Exceptions = &vp + } + } + } + + return nil +} + +// ParseGetMap builds a GetMapKVP object based on a GetMap struct +func (gmkvp *GetMapKVP) ParseGetMap(gm *GetMap) ows.Exception { + gmkvp.Request = getmap + gmkvp.Version = Version + gmkvp.Service = Service + gmkvp.Layers = gm.StyledLayerDescriptor.getLayerKVPValue() + gmkvp.Styles = gm.StyledLayerDescriptor.getStyleKVPValue() + gmkvp.CRS = gm.CRS + gmkvp.Bbox = gm.BoundingBox.BuildKVP() + gmkvp.Width = strconv.Itoa(gm.Output.Size.Width) + gmkvp.Height = strconv.Itoa(gm.Output.Size.Height) + gmkvp.Format = gm.Output.Format + + if gm.Output.Transparent != nil { + t := *gm.Output.Transparent + tp := strconv.FormatBool(t) + gmkvp.Transparent = &tp + } + + if gm.Output.BGcolor != nil { + gmkvp.BGColor = gm.Output.BGcolor + } + + // TODO: something with Time & Elevation + // gmkvp.Time = gm.Time + // gmkvp.Elevation = gm.Elevation + + gmkvp.Exceptions = gm.Exceptions + + return nil +} + +// BuildOutput builds a Output struct from the KVP information +func (gmkvp *GetMapKVP) BuildOutput() (Output, ows.Exception) { + output := Output{} + + h, err := strconv.Atoi(gmkvp.Height) + if err != nil { + return output, ows.InvalidParameterValue(HEIGHT, gmkvp.Height) + } + w, err := strconv.Atoi(gmkvp.Width) + if err != nil { + return output, ows.InvalidParameterValue(WIDTH, gmkvp.Width) + } + + output.Size = Size{Height: h, Width: w} + output.Format = gmkvp.Format + if b, err := strconv.ParseBool(*gmkvp.Transparent); err == nil { + output.Transparent = &b + } + output.BGcolor = gmkvp.BGColor + + return output, nil +} + +// BuildStyledLayerDescriptor builds a StyledLayerDescriptor struct from the KVP information +func (gmkvp *GetMapKVP) BuildStyledLayerDescriptor() (StyledLayerDescriptor, ows.Exception) { + var layers, styles []string + if gmkvp.Layers != `` { + layers = strings.Split(gmkvp.Layers, ",") + } + if gmkvp.Styles != `` { + styles = strings.Split(gmkvp.Styles, ",") + } + + sld, err := buildStyledLayerDescriptor(layers, styles) + if err != nil { + return sld, err + } + + return sld, nil +} + +// BuildKVP builds a url.Values query from a GetMapKVP struct +func (gmkvp *GetMapKVP) BuildKVP() url.Values { + query := make(map[string][]string) + + query[SERVICE] = []string{gmkvp.Service} + query[VERSION] = []string{gmkvp.Version} + query[REQUEST] = []string{gmkvp.Request} + query[LAYERS] = []string{gmkvp.Layers} + query[STYLES] = []string{gmkvp.Styles} + query[CRS] = []string{gmkvp.CRS} + query[BBOX] = []string{gmkvp.Bbox} + query[WIDTH] = []string{gmkvp.Width} + query[HEIGHT] = []string{gmkvp.Height} + query[FORMAT] = []string{gmkvp.Format} + + if gmkvp.Transparent != nil { + query[TRANSPARENT] = []string{*gmkvp.Transparent} + } + if gmkvp.BGColor != nil { + query[BGCOLOR] = []string{*gmkvp.BGColor} + } + if gmkvp.Exceptions != nil { + query[EXCEPTIONS] = []string{*gmkvp.Exceptions} + } + + return query +} From ac4caefa9713eac96311904ab87d1dc9afddbb5c Mon Sep 17 00:00:00 2001 From: Wouter Date: Tue, 4 Aug 2020 20:35:36 +0200 Subject: [PATCH 24/45] go mod tidy: remove unused yaml.v2, regarding performance we do explicit conversion --- go.mod | 2 -- go.sum | 4 ---- 2 files changed, 6 deletions(-) diff --git a/go.mod b/go.mod index a025d98..a1e29a0 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,3 @@ module github.com/pdok/ogc-specifications go 1.14 - -require gopkg.in/yaml.v2 v2.3.0 diff --git a/go.sum b/go.sum index 168980d..e69de29 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +0,0 @@ -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= From a6b40a5e2bbca97e18d3fc26b6159b10af07f752 Mon Sep 17 00:00:00 2001 From: Wouter Date: Tue, 4 Aug 2020 20:40:14 +0200 Subject: [PATCH 25/45] removed validation tags from struct --- pkg/wcs201/request/getcapabilities.go | 6 +++--- pkg/wfs200/request/common.go | 4 ++-- pkg/wfs200/request/getcapabilities.go | 4 ++-- pkg/wms130/exception/exception.go | 6 +++--- pkg/wms130/request/common.go | 4 ++-- pkg/wms130/request/getcapabilities.go | 6 +++--- pkg/wms130/request/getfeatureinfo.go | 16 ++++++++-------- pkg/wms130/request/getmap.go | 26 +++++++++++++------------- pkg/wmts100/request/getcapabilities.go | 6 +++--- 9 files changed, 39 insertions(+), 39 deletions(-) diff --git a/pkg/wcs201/request/getcapabilities.go b/pkg/wcs201/request/getcapabilities.go index 8e5679d..b4e4ced 100644 --- a/pkg/wcs201/request/getcapabilities.go +++ b/pkg/wcs201/request/getcapabilities.go @@ -97,8 +97,8 @@ func (gc *GetCapabilities) BuildXML() []byte { // GetCapabilities struct with the needed parameters/attributes needed for making a GetCapabilities request type GetCapabilities struct { - XMLName xml.Name `xml:"GetCapabilities" yaml:"getcapabilities" validate:"required"` - Service string `xml:"service,attr" yaml:"service" validate:"required,oneof=WCS wcs"` - Version string `xml:"version,attr" yaml:"version" validate:"eq=2.0.1"` + XMLName xml.Name `xml:"GetCapabilities" yaml:"getcapabilities"` + Service string `xml:"service,attr" yaml:"service"` + Version string `xml:"version,attr" yaml:"version"` Attr ows.XMLAttribute `xml:",attr"` } diff --git a/pkg/wfs200/request/common.go b/pkg/wfs200/request/common.go index b602f53..2e24b9f 100644 --- a/pkg/wfs200/request/common.go +++ b/pkg/wfs200/request/common.go @@ -24,8 +24,8 @@ const ( // BaseRequest based on Table 5 WFS2.0.0 spec // Note: not usable for GetCapabilities request regarding deviation of Optional/Mandatory parameters SERVICE and VERSION type BaseRequest struct { - Service string `xml:"service,attr" yaml:"service,omitempty" validate:"oneof=WFS wfs"` - Version string `xml:"version,attr" yaml:"version" validate:"required,eq=2.0.0"` + Service string `xml:"service,attr" yaml:"service,omitempty"` + Version string `xml:"version,attr" yaml:"version"` Attr ows.XMLAttribute `xml:",attr"` } diff --git a/pkg/wfs200/request/getcapabilities.go b/pkg/wfs200/request/getcapabilities.go index f09e7d0..fd4b53f 100644 --- a/pkg/wfs200/request/getcapabilities.go +++ b/pkg/wfs200/request/getcapabilities.go @@ -87,7 +87,7 @@ func (gc *GetCapabilities) BuildXML() []byte { // GetCapabilities struct with the needed parameters/attributes needed for making a GetCapabilities request type GetCapabilities struct { XMLName xml.Name `xml:"GetCapabilities" yaml:"getcapabilities"` - Service string `xml:"service,attr" yaml:"service" validate:"required,oneof=WFS wfs"` - Version string `xml:"version,attr" yaml:"version" validate:"eq=2.0.0"` + Service string `xml:"service,attr" yaml:"service"` + Version string `xml:"version,attr" yaml:"version"` Attr ows.XMLAttribute `xml:",attr"` } diff --git a/pkg/wms130/exception/exception.go b/pkg/wms130/exception/exception.go index 6093d20..f8a77d0 100644 --- a/pkg/wms130/exception/exception.go +++ b/pkg/wms130/exception/exception.go @@ -38,9 +38,9 @@ func (r WMSServiceExceptionReport) Report(errors []ows.Exception) []byte { // WMSException grouping the error message variables together type WMSException struct { - ExceptionText string `xml:",chardata" yaml:"exception"` - ExceptionCode string `xml:"code,attr" yaml:"code"` - LocatorCode string `xml:"locator,attr,omitempty" yaml:"locator,omitempty"` + ExceptionText string `xml:",chardata" yaml:"exception"` + ExceptionCode string `xml:"code,attr" yaml:"code"` + LocatorCode string `xml:"locator,attr,omitempty" yaml:"locator,omitempty"` } // Error returns available ExceptionText diff --git a/pkg/wms130/request/common.go b/pkg/wms130/request/common.go index ef45193..fc19ebd 100644 --- a/pkg/wms130/request/common.go +++ b/pkg/wms130/request/common.go @@ -23,8 +23,8 @@ const ( // http://schemas.opengis.net/sld/1.1//example_getmap.xml // Note: not usable for GetCapabilities request regarding deviation of Optional/Mandatory parameters SERVICE and VERSION type BaseRequest struct { - Service string `xml:"service,attr" yaml:"service,omitempty" validate:"oneof=WMS wms"` - Version string `xml:"version,attr" yaml:"version" validate:"required,eq=1.3.0"` + Service string `xml:"service,attr" yaml:"service,omitempty"` + Version string `xml:"version,attr" yaml:"version"` Attr ows.XMLAttribute `xml:",attr"` } diff --git a/pkg/wms130/request/getcapabilities.go b/pkg/wms130/request/getcapabilities.go index df7da01..3bb50f7 100644 --- a/pkg/wms130/request/getcapabilities.go +++ b/pkg/wms130/request/getcapabilities.go @@ -84,8 +84,8 @@ func (gc *GetCapabilities) BuildXML() []byte { // GetCapabilities struct with the needed parameters/attributes needed for making a GetCapabilities request type GetCapabilities struct { - XMLName xml.Name `xml:"GetCapabilities" yaml:"getcapabilities" validate:"required"` - Service string `xml:"service,attr" yaml:"service" validate:"required,oneof=WMS wms"` - Version string `xml:"version,attr" yaml:"version" validate:"eq=1.3.0"` + XMLName xml.Name `xml:"GetCapabilities" yaml:"getcapabilities"` + Service string `xml:"service,attr" yaml:"service"` + Version string `xml:"version,attr" yaml:"version"` Attr ows.XMLAttribute `xml:",attr"` } diff --git a/pkg/wms130/request/getfeatureinfo.go b/pkg/wms130/request/getfeatureinfo.go index 74aadfe..1893139 100644 --- a/pkg/wms130/request/getfeatureinfo.go +++ b/pkg/wms130/request/getfeatureinfo.go @@ -238,21 +238,21 @@ func (gfi *GetFeatureInfo) BuildXML() []byte { // GetFeatureInfo struct with the needed parameters/attributes needed for making a GetFeatureInfo request type GetFeatureInfo struct { - XMLName xml.Name `xml:"GetFeatureInfo" yaml:"getfeatureinfo" validate:"required"` + XMLName xml.Name `xml:"GetFeatureInfo" yaml:"getfeatureinfo"` BaseRequest // // These are the 'minimum' required GetMap parameters // needed in a GetFeatureInfo request - StyledLayerDescriptor StyledLayerDescriptor `xml:"StyledLayerDescriptor" yaml:"styledlayerdescriptor" validate:"required"` - CRS string `xml:"CRS" yaml:"crs" validate:"required"` - BoundingBox ows.BoundingBox `xml:"BoundingBox" yaml:"boundingbox" validate:"required"` + StyledLayerDescriptor StyledLayerDescriptor `xml:"StyledLayerDescriptor" yaml:"styledlayerdescriptor"` + CRS string `xml:"CRS" yaml:"crs"` + BoundingBox ows.BoundingBox `xml:"BoundingBox" yaml:"boundingbox"` // We skip the Output struct, because these are not required parameters - Size Size `xml:"Size" yaml:"size" validate:"required"` + Size Size `xml:"Size" yaml:"size"` - QueryLayers []string `xml:"QueryLayers" yaml:"querylayers" validate:"required"` - I int `xml:"I" yaml:"i" validate:"required"` - J int `xml:"J" yaml:"j" validate:"required"` + QueryLayers []string `xml:"QueryLayers" yaml:"querylayers"` + I int `xml:"I" yaml:"i"` + J int `xml:"J" yaml:"j"` InfoFormat *string `xml:"InfoFormat" yaml:"infoformat"` FeatureCount *int `xml:"FeatureCount" yaml:"featurecount"` Exceptions *string `xml:"Exceptions" yaml:"exceptions"` diff --git a/pkg/wms130/request/getmap.go b/pkg/wms130/request/getmap.go index f6be548..8b22aeb 100644 --- a/pkg/wms130/request/getmap.go +++ b/pkg/wms130/request/getmap.go @@ -213,12 +213,12 @@ func (sld *StyledLayerDescriptor) GetNamedStyles() []string { // GetMap struct with the needed parameters/attributes needed for making a GetMap request // Struct based on http://schemas.opengis.net/sld/1.1/example_getmap.xml type GetMap struct { - XMLName xml.Name `xml:"GetMap" yaml:"getmap" validate:"required"` + XMLName xml.Name `xml:"GetMap" yaml:"getmap"` BaseRequest - StyledLayerDescriptor StyledLayerDescriptor `xml:"StyledLayerDescriptor" yaml:"styledlayerdescriptor" validate:"required"` - CRS string `xml:"CRS" yaml:"crs" validate:"required"` - BoundingBox ows.BoundingBox `xml:"BoundingBox" yaml:"boundingbox" validate:"required"` - Output Output `xml:"Output" yaml:"output" validate:"required"` + StyledLayerDescriptor StyledLayerDescriptor `xml:"StyledLayerDescriptor" yaml:"styledlayerdescriptor"` + CRS string `xml:"CRS" yaml:"crs"` + BoundingBox ows.BoundingBox `xml:"BoundingBox" yaml:"boundingbox"` + Output Output `xml:"Output" yaml:"output"` Exceptions *string `xml:"Exceptions" yaml:"exceptions"` // TODO: something with Time & Elevation // Elevation *[]Elevation `xml:"Elevation" yaml:"elevation"` @@ -253,22 +253,22 @@ func (output *Output) Validate(c capabilities.Capability) ows.Exceptions { // Output struct type Output struct { - Size Size `xml:"Size" yaml:"size" validate:"required"` - Format string `xml:"Format" yaml:"format" validate:"required"` + Size Size `xml:"Size" yaml:"size"` + Format string `xml:"Format" yaml:"format"` Transparent *bool `xml:"Transparent" yaml:"transparent"` BGcolor *string `xml:"BGcolor" yaml:"bgcolor"` } // Size struct type Size struct { - Width int `xml:"Width" yaml:"width" validate:"required,min=1,max=5000"` - Height int `xml:"Height" yaml:"height" validate:"required,min=1,max=5000"` + Width int `xml:"Width" yaml:"width"` + Height int `xml:"Height" yaml:"height"` } // StyledLayerDescriptor struct type StyledLayerDescriptor struct { - Version string `xml:"version,attr" yaml:"version" validate:"required,eq=1.1.0"` - NamedLayer []NamedLayer `xml:"NamedLayer" yaml:"namedlayer" validate:"required"` + Version string `xml:"version,attr" yaml:"version"` + NamedLayer []NamedLayer `xml:"NamedLayer" yaml:"namedlayer"` } // Validate the StyledLayerDescriptor @@ -318,13 +318,13 @@ func (sld *StyledLayerDescriptor) Validate(c capabilities.Capability) ows.Except // NamedLayer struct type NamedLayer struct { - Name string `xml:"Name" yaml:"name" validate:"required"` + Name string `xml:"Name" yaml:"name"` NamedStyle *NamedStyle `xml:"NamedStyle" yaml:"namedstyle"` } // NamedStyle contains the style name that needs be applied type NamedStyle struct { - Name string `xml:"Name" yaml:"name" validate:"required"` + Name string `xml:"Name" yaml:"name"` } // Elevation struct for GetMap requests diff --git a/pkg/wmts100/request/getcapabilities.go b/pkg/wmts100/request/getcapabilities.go index 7339c08..1fc465e 100644 --- a/pkg/wmts100/request/getcapabilities.go +++ b/pkg/wmts100/request/getcapabilities.go @@ -97,8 +97,8 @@ func (gc *GetCapabilities) BuildXML() []byte { // GetCapabilities struct with the needed parameters/attributes needed for making a GetCapabilities request type GetCapabilities struct { - XMLName xml.Name `xml:"GetCapabilities" yaml:"getcapabilities" validate:"required"` - Service string `xml:"service,attr" yaml:"service" validate:"required,oneof=WMTS wmts"` - Version string `xml:"version,attr" yaml:"version" validate:"eq=1.0.0"` + XMLName xml.Name `xml:"GetCapabilities" yaml:"getcapabilities"` + Service string `xml:"service,attr" yaml:"service"` + Version string `xml:"version,attr" yaml:"version"` Attr ows.XMLAttribute `xml:",attr"` } From 127c328e5b383983792c7733a334e5cbb278e9d4 Mon Sep 17 00:00:00 2001 From: Wouter Date: Tue, 4 Aug 2020 20:52:07 +0200 Subject: [PATCH 26/45] interface ows.Exception -> ows.Exceptions --- pkg/ows/operationkvprequest.go | 2 +- pkg/ows/operationrequest.go | 4 ++-- pkg/wcs201/request/getcapabilities.go | 8 +++---- pkg/wfs200/request/common.go | 4 ++-- pkg/wfs200/request/describefeaturetype.go | 4 ++-- .../request/describefeaturetype_test.go | 8 +++---- pkg/wfs200/request/getcapabilities.go | 2 +- pkg/wfs200/request/getcapabilities_test.go | 2 +- pkg/wfs200/request/getfeature.go | 4 ++-- pkg/wfs200/request/getfeature_test.go | 2 +- pkg/wms130/request/common.go | 4 ++-- pkg/wms130/request/getcapabilities.go | 8 +++---- pkg/wms130/request/getcapabilities_test.go | 4 ++-- pkg/wms130/request/getfeatureinfo.go | 22 +++++++++---------- pkg/wms130/request/getfeatureinfo_test.go | 4 ++-- pkg/wms130/request/getmap.go | 10 ++++----- pkg/wms130/request/getmap_test.go | 4 ++-- pkg/wms130/request/getmapkvp.go | 2 +- pkg/wmts100/request/getcapabilities.go | 8 +++---- 19 files changed, 53 insertions(+), 53 deletions(-) diff --git a/pkg/ows/operationkvprequest.go b/pkg/ows/operationkvprequest.go index 2592a09..953dde4 100644 --- a/pkg/ows/operationkvprequest.go +++ b/pkg/ows/operationkvprequest.go @@ -7,6 +7,6 @@ type OperationKVPRequest interface { Type() string Validate(Capability) Exceptions - ParseKVP(url.Values) Exception + ParseKVP(url.Values) Exceptions BuildKVP() url.Values } diff --git a/pkg/ows/operationrequest.go b/pkg/ows/operationrequest.go index f0171a1..d68768d 100644 --- a/pkg/ows/operationrequest.go +++ b/pkg/ows/operationrequest.go @@ -7,8 +7,8 @@ type OperationRequest interface { Type() string Validate(Capability) Exceptions - ParseXML([]byte) Exception - ParseKVP(url.Values) Exception + ParseXML([]byte) Exceptions + ParseKVP(url.Values) Exceptions BuildXML() []byte BuildKVP() url.Values diff --git a/pkg/wcs201/request/getcapabilities.go b/pkg/wcs201/request/getcapabilities.go index b4e4ced..974563c 100644 --- a/pkg/wcs201/request/getcapabilities.go +++ b/pkg/wcs201/request/getcapabilities.go @@ -39,13 +39,13 @@ func (gc *GetCapabilities) Validate(c capabilities.Capability) ows.Exceptions { } // ParseXML builds a GetCapabilities object based on a XML document -func (gc *GetCapabilities) ParseXML(body []byte) ows.Exception { +func (gc *GetCapabilities) ParseXML(body []byte) ows.Exceptions { var xmlattributes ows.XMLAttribute if err := xml.Unmarshal(body, &xmlattributes); err != nil { - return ows.MissingParameterValue() + return ows.Exceptions{ows.MissingParameterValue()} } if err := xml.Unmarshal(body, &gc); err != nil { - return ows.MissingParameterValue("REQUEST") + return ows.Exceptions{ows.MissingParameterValue("REQUEST")} } var n []xml.Attr for _, a := range xmlattributes { @@ -62,7 +62,7 @@ func (gc *GetCapabilities) ParseXML(body []byte) ows.Exception { } // ParseKVP builds a GetCapabilities object based on the available query parameters -func (gc *GetCapabilities) ParseKVP(query url.Values) ows.Exception { +func (gc *GetCapabilities) ParseKVP(query url.Values) ows.Exceptions { for k, v := range query { switch strings.ToUpper(k) { case REQUEST: diff --git a/pkg/wfs200/request/common.go b/pkg/wfs200/request/common.go index 2e24b9f..4937612 100644 --- a/pkg/wfs200/request/common.go +++ b/pkg/wfs200/request/common.go @@ -30,7 +30,7 @@ type BaseRequest struct { } // ParseKVP builds a BaseRequest Struct based on the given parameters -func (b *BaseRequest) ParseKVP(query url.Values) ows.Exception { +func (b *BaseRequest) ParseKVP(query url.Values) ows.Exceptions { if len(query[SERVICE]) > 0 { // Service is optional, because it's implicit for a GetFeature/DescribeFeatureType request b.Service = query[SERVICE][0] @@ -39,7 +39,7 @@ func (b *BaseRequest) ParseKVP(query url.Values) ows.Exception { b.Version = query[VERSION][0] } else { // Version is mandatory - return ows.MissingParameterValue(VERSION) + return ows.Exceptions{ows.MissingParameterValue(VERSION)} } return nil } diff --git a/pkg/wfs200/request/describefeaturetype.go b/pkg/wfs200/request/describefeaturetype.go index 31adb91..b50844d 100644 --- a/pkg/wfs200/request/describefeaturetype.go +++ b/pkg/wfs200/request/describefeaturetype.go @@ -53,11 +53,11 @@ func (dft *DescribeFeatureType) ParseXML(doc []byte) ows.Exception { } // ParseKVP builds a DescribeFeatureType object based on the available query parameters -func (dft *DescribeFeatureType) ParseKVP(query url.Values) ows.Exception { +func (dft *DescribeFeatureType) ParseKVP(query url.Values) ows.Exceptions { if len(query) == 0 { // When there are no query value we know that at least // the manadorty VERSION parameter is missing. - return ows.MissingParameterValue(VERSION) + return ows.Exceptions{ows.MissingParameterValue(VERSION)} } q := utils.KeysToUpper(query) diff --git a/pkg/wfs200/request/describefeaturetype_test.go b/pkg/wfs200/request/describefeaturetype_test.go index a3c4789..f2718d7 100644 --- a/pkg/wfs200/request/describefeaturetype_test.go +++ b/pkg/wfs200/request/describefeaturetype_test.go @@ -103,14 +103,14 @@ func TestDescribeFeatureTypeParseKVP(t *testing.T) { var tests = []struct { Query url.Values Result DescribeFeatureType - Exception ows.Exception + Exception ows.Exceptions }{ // "Normal" query request with UPPER/lower/MiXeD case 0: {Query: map[string][]string{"SERVICE": {Service}, "Request": {describefeaturetype}, "version": {"2.0.0"}}, Result: DescribeFeatureType{XMLName: xml.Name{Local: describefeaturetype}, BaseRequest: BaseRequest{Service: "WFS", Version: "2.0.0"}}}, // Missing mandatory SERVICE attribute 1: {Query: map[string][]string{"Request": {describefeaturetype}}, - Exception: ows.MissingParameterValue(VERSION)}, + Exception: ows.Exceptions{ows.MissingParameterValue(VERSION)}}, // Missing optional VERSION attribute 2: {Query: map[string][]string{"SERVICE": {"WFS"}, "Request": {describefeaturetype}, "Version": {"2.0.0"}}, Result: DescribeFeatureType{XMLName: xml.Name{Local: describefeaturetype}, BaseRequest: BaseRequest{Service: "WFS", Version: Version}}}, @@ -125,7 +125,7 @@ func TestDescribeFeatureTypeParseKVP(t *testing.T) { BaseDescribeFeatureTypeRequest: BaseDescribeFeatureTypeRequest{TypeName: sp("acme:anvils")}, BaseRequest: BaseRequest{Service: Service, Version: Version}}}, 6: {Query: map[string][]string{}, - Exception: ows.MissingParameterValue(VERSION), + Exception: ows.Exceptions{ows.MissingParameterValue(VERSION)}, }, } @@ -133,7 +133,7 @@ func TestDescribeFeatureTypeParseKVP(t *testing.T) { var dft DescribeFeatureType err := dft.ParseKVP(n.Query) if err != nil { - if err.Error() != n.Exception.Error() { + if err[0].Error() != n.Exception[0].Error() { t.Errorf("test: %d, expected: %s,\n got: %s", k, n.Exception, err) } } else { diff --git a/pkg/wfs200/request/getcapabilities.go b/pkg/wfs200/request/getcapabilities.go index fd4b53f..f642da0 100644 --- a/pkg/wfs200/request/getcapabilities.go +++ b/pkg/wfs200/request/getcapabilities.go @@ -51,7 +51,7 @@ func (gc *GetCapabilities) ParseXML(doc []byte) ows.Exception { } // ParseKVP builds a GetCapabilities object based on the available query parameters -func (gc *GetCapabilities) ParseKVP(query url.Values) ows.Exception { +func (gc *GetCapabilities) ParseKVP(query url.Values) ows.Exceptions { for k, v := range query { switch strings.ToUpper(k) { case REQUEST: diff --git a/pkg/wfs200/request/getcapabilities_test.go b/pkg/wfs200/request/getcapabilities_test.go index a6cbb4b..ad9e4e4 100644 --- a/pkg/wfs200/request/getcapabilities_test.go +++ b/pkg/wfs200/request/getcapabilities_test.go @@ -114,7 +114,7 @@ func TestGetCapabilitiesParseKVP(t *testing.T) { var gc GetCapabilities err := gc.ParseKVP(n.Query) if err != nil { - if err.Error() != n.Error.Error() { + if err[0].Error() != n.Error.Error() { t.Errorf("test: %d, expected: %s,\n got: %s", k, n.Error, err) } } else { diff --git a/pkg/wfs200/request/getfeature.go b/pkg/wfs200/request/getfeature.go index fd79f3a..c7603c0 100644 --- a/pkg/wfs200/request/getfeature.go +++ b/pkg/wfs200/request/getfeature.go @@ -87,11 +87,11 @@ func (gf *GetFeature) ParseXML(doc []byte) ows.Exception { // ParseKVP builds a GetCapabilities object based on the available query parameters // All the keys from the query url.Values need to be UpperCase, this is done during the execution of the operations.ValidRequest() -func (gf *GetFeature) ParseKVP(query url.Values) ows.Exception { +func (gf *GetFeature) ParseKVP(query url.Values) ows.Exceptions { if len(query) == 0 { // When there are no query value we know that at least // the manadorty VERSION parameter is missing. - return ows.MissingParameterValue(VERSION) + return ows.Exceptions{ows.MissingParameterValue(VERSION)} } q := utils.KeysToUpper(query) diff --git a/pkg/wfs200/request/getfeature_test.go b/pkg/wfs200/request/getfeature_test.go index f38dd02..32398a2 100644 --- a/pkg/wfs200/request/getfeature_test.go +++ b/pkg/wfs200/request/getfeature_test.go @@ -265,7 +265,7 @@ func TestGetFeatureParseKVP(t *testing.T) { for tid, q := range tests { var gf GetFeature if err := gf.ParseKVP(q.QueryParams); err != nil { - if err != q.Exception { + if err[0] != q.Exception { t.Errorf("test: %d, expected: %+v ,\n got: %+v", tid, q.Exception, err) } } else { diff --git a/pkg/wms130/request/common.go b/pkg/wms130/request/common.go index fc19ebd..4c962df 100644 --- a/pkg/wms130/request/common.go +++ b/pkg/wms130/request/common.go @@ -29,7 +29,7 @@ type BaseRequest struct { } // ParseKVP builds a BaseRequest struct based on the given parameters -func (b *BaseRequest) ParseKVP(query url.Values) ows.Exception { +func (b *BaseRequest) ParseKVP(query url.Values) ows.Exceptions { if len(query[SERVICE]) > 0 { // Service is optional, because it's implicit for a GetMap/GetFeatureInfo request b.Service = query[SERVICE][0] @@ -38,7 +38,7 @@ func (b *BaseRequest) ParseKVP(query url.Values) ows.Exception { b.Version = query[VERSION][0] } else { // Version is mandatory - return ows.MissingParameterValue(VERSION) + return ows.Exceptions{ows.MissingParameterValue(VERSION)} } return nil } diff --git a/pkg/wms130/request/getcapabilities.go b/pkg/wms130/request/getcapabilities.go index 3bb50f7..c003286 100644 --- a/pkg/wms130/request/getcapabilities.go +++ b/pkg/wms130/request/getcapabilities.go @@ -26,13 +26,13 @@ func (gc *GetCapabilities) Validate(c capabilities.Capability) ows.Exceptions { } // ParseXML builds a GetCapabilities object based on a XML document -func (gc *GetCapabilities) ParseXML(body []byte) ows.Exception { +func (gc *GetCapabilities) ParseXML(body []byte) ows.Exceptions { var xmlattributes ows.XMLAttribute if err := xml.Unmarshal(body, &xmlattributes); err != nil { - return ows.MissingParameterValue() + return ows.Exceptions{ows.MissingParameterValue()} } if err := xml.Unmarshal(body, &gc); err != nil { - return ows.MissingParameterValue("REQUEST") + return ows.Exceptions{ows.MissingParameterValue("REQUEST")} } var n []xml.Attr for _, a := range xmlattributes { @@ -49,7 +49,7 @@ func (gc *GetCapabilities) ParseXML(body []byte) ows.Exception { } // ParseKVP builds a GetCapabilities object based on the available query parameters -func (gc *GetCapabilities) ParseKVP(query url.Values) ows.Exception { +func (gc *GetCapabilities) ParseKVP(query url.Values) ows.Exceptions { for k, v := range query { switch strings.ToUpper(k) { case REQUEST: diff --git a/pkg/wms130/request/getcapabilities_test.go b/pkg/wms130/request/getcapabilities_test.go index 8d78d61..e078881 100644 --- a/pkg/wms130/request/getcapabilities_test.go +++ b/pkg/wms130/request/getcapabilities_test.go @@ -37,7 +37,7 @@ func TestGetCapabilitiesParseXML(t *testing.T) { var gc GetCapabilities err := gc.ParseXML(n.Body) if err != nil { - if err.Error() != n.Error.Error() { + if err[0].Error() != n.Error.Error() { t.Errorf("test: %d, expected: %s,\n got: %s", k, n.Error, err) } } else { @@ -96,7 +96,7 @@ func TestGetCapabilitiesParseKVP(t *testing.T) { var gc GetCapabilities err := gc.ParseKVP(n.Query) if err != nil { - if err.Error() != n.Error.Error() { + if err[0].Error() != n.Error.Error() { t.Errorf("test: %d, expected: %s,\n got: %s", k, n.Error, err) } } else { diff --git a/pkg/wms130/request/getfeatureinfo.go b/pkg/wms130/request/getfeatureinfo.go index 1893139..c9ce564 100644 --- a/pkg/wms130/request/getfeatureinfo.go +++ b/pkg/wms130/request/getfeatureinfo.go @@ -48,13 +48,13 @@ func (gfi *GetFeatureInfo) Validate(c capabilities.Capability) ows.Exceptions { // Note: the XML GetFeatureInfo body that is consumed is a interpretation. // So we use the GetMap, that is a large part of this request, as a base // with the additional GetFeatureInfo parameters. -func (gfi *GetFeatureInfo) ParseXML(body []byte) ows.Exception { +func (gfi *GetFeatureInfo) ParseXML(body []byte) ows.Exceptions { var xmlattributes ows.XMLAttribute if err := xml.Unmarshal(body, &xmlattributes); err != nil { - return ows.MissingParameterValue() + return ows.Exceptions{ows.MissingParameterValue()} } if err := xml.Unmarshal(body, &gfi); err != nil { - return ows.MissingParameterValue("REQUEST") + return ows.Exceptions{ows.MissingParameterValue("REQUEST")} } var n []xml.Attr for _, a := range xmlattributes { @@ -71,11 +71,11 @@ func (gfi *GetFeatureInfo) ParseXML(body []byte) ows.Exception { } // ParseKVP builds a GetFeatureInfo object based on the available query parameters -func (gfi *GetFeatureInfo) ParseKVP(query url.Values) ows.Exception { +func (gfi *GetFeatureInfo) ParseKVP(query url.Values) ows.Exceptions { if len(query) == 0 { // When there are no query value we know that at least // the manadorty VERSION parameter is missing. - return ows.MissingParameterValue(VERSION) + return ows.Exceptions{ows.MissingParameterValue(VERSION)} } q := utils.KeysToUpper(query) @@ -106,20 +106,20 @@ func (gfi *GetFeatureInfo) ParseKVP(query url.Values) ows.Exception { case BBOX: var bbox ows.BoundingBox if err := bbox.Build(query[k][0]); err != nil { - return err + return ows.Exceptions{err} } gfi.BoundingBox = bbox case WIDTH: i, err := strconv.Atoi(query[k][0]) if err != nil { - return ows.MissingParameterValue(WIDTH, query[k][0]) + return ows.Exceptions{ows.MissingParameterValue(WIDTH, query[k][0])} } gfi.Size.Width = i case HEIGHT: i, err := strconv.Atoi(query[k][0]) if err != nil { // TODO: ignore or a exception - return ows.MissingParameterValue(HEIGHT, query[k][0]) + return ows.Exceptions{ows.MissingParameterValue(HEIGHT, query[k][0])} } gfi.Size.Height = i case QUERYLAYERS: @@ -127,13 +127,13 @@ func (gfi *GetFeatureInfo) ParseKVP(query url.Values) ows.Exception { case I: i, err := strconv.Atoi(query[k][0]) if err != nil { - return exception.InvalidPoint(query[I][0], query[J][0]) + return ows.Exceptions{exception.InvalidPoint(query[I][0], query[J][0])} } gfi.I = i case J: j, err := strconv.Atoi(query[k][0]) if err != nil { - return exception.InvalidPoint(query[I][0], query[J][0]) + return ows.Exceptions{exception.InvalidPoint(query[I][0], query[J][0])} } gfi.J = j } @@ -144,7 +144,7 @@ func (gfi *GetFeatureInfo) ParseKVP(query url.Values) ows.Exception { if err == nil { gfi.StyledLayerDescriptor = sld } else { - return err + return ows.Exceptions{err} } // GetFeatureInfo optional parameters diff --git a/pkg/wms130/request/getfeatureinfo_test.go b/pkg/wms130/request/getfeatureinfo_test.go index a91a0af..7ffee76 100644 --- a/pkg/wms130/request/getfeatureinfo_test.go +++ b/pkg/wms130/request/getfeatureinfo_test.go @@ -221,7 +221,7 @@ func TestGetFeatureInfoParseKVP(t *testing.T) { var gfi GetFeatureInfo err := gfi.ParseKVP(n.Query) if err != nil { - if err.Error() != n.Error.Error() { + if err[0].Error() != n.Error.Error() { t.Errorf("test: %d, expected: %s,\n got: %s", k, n.Error, err) } } else { @@ -307,7 +307,7 @@ func TestGetFeatureInfoParseXML(t *testing.T) { var gm GetFeatureInfo err := gm.ParseXML(n.Body) if err != nil { - if err.Error() != n.Error.Error() { + if err[0].Error() != n.Error.Error() { t.Errorf("test: %d, expected: %s,\n got: %s", k, n.Error, err) } } else { diff --git a/pkg/wms130/request/getmap.go b/pkg/wms130/request/getmap.go index 8b22aeb..f5e21d0 100644 --- a/pkg/wms130/request/getmap.go +++ b/pkg/wms130/request/getmap.go @@ -79,11 +79,11 @@ func (gm *GetMap) ParseGetMapKVP(gmkvp GetMapKVP) ows.Exception { } // ParseKVP builds a GetMap object based on the available query parameters -func (gm *GetMap) ParseKVP(query url.Values) ows.Exception { +func (gm *GetMap) ParseKVP(query url.Values) ows.Exceptions { if len(query) == 0 { // When there are no query values we know that at least // the manadorty VERSION parameter is missing. - return ows.MissingParameterValue(VERSION) + return ows.Exceptions{ows.MissingParameterValue(VERSION)} } gmkvp := GetMapKVP{} @@ -92,17 +92,17 @@ func (gm *GetMap) ParseKVP(query url.Values) ows.Exception { } if err := gm.ParseGetMapKVP(gmkvp); err != nil { - return err + return ows.Exceptions{err} } return nil } // ParseXML builds a GetMap object based on a XML document -func (gm *GetMap) ParseXML(body []byte) ows.Exception { +func (gm *GetMap) ParseXML(body []byte) ows.Exceptions { var xmlattributes ows.XMLAttribute if err := xml.Unmarshal(body, &xmlattributes); err != nil { - return ows.MissingParameterValue() + return ows.Exceptions{ows.MissingParameterValue()} } xml.Unmarshal(body, &gm) //When object can be Unmarshalled -> XMLAttributes, it can be Unmarshalled -> GetMap var n []xml.Attr diff --git a/pkg/wms130/request/getmap_test.go b/pkg/wms130/request/getmap_test.go index 37eed0d..9acd4e9 100644 --- a/pkg/wms130/request/getmap_test.go +++ b/pkg/wms130/request/getmap_test.go @@ -199,7 +199,7 @@ func TestGetMapParseXML(t *testing.T) { var gm GetMap err := gm.ParseXML(n.Body) if err != nil { - if err.Error() != n.Error.Error() { + if err[0].Error() != n.Error.Error() { t.Errorf("test: %d, expected: %s,\n got: %s", k, n.Error, err) } } else { @@ -320,7 +320,7 @@ func TestGetMapParseKVP(t *testing.T) { var gm GetMap err := gm.ParseKVP(n.Query) if err != nil { - if err.Error() != n.Exception.Error() { + if err[0].Error() != n.Exception.Error() { t.Errorf("test: %d, expected: %s,\n got: %s", k, n.Exception, err) } } else { diff --git a/pkg/wms130/request/getmapkvp.go b/pkg/wms130/request/getmapkvp.go index f81c85c..7b77e29 100644 --- a/pkg/wms130/request/getmapkvp.go +++ b/pkg/wms130/request/getmapkvp.go @@ -40,7 +40,7 @@ type GetMapKVPOptional struct { } // ParseKVP builds a GetMapKVP object based on the available query parameters -func (gmkvp *GetMapKVP) ParseKVP(query url.Values) ows.Exception { +func (gmkvp *GetMapKVP) ParseKVP(query url.Values) ows.Exceptions { var exceptions ows.Exceptions for k, v := range query { if len(v) != 1 { diff --git a/pkg/wmts100/request/getcapabilities.go b/pkg/wmts100/request/getcapabilities.go index 1fc465e..71bb869 100644 --- a/pkg/wmts100/request/getcapabilities.go +++ b/pkg/wmts100/request/getcapabilities.go @@ -39,13 +39,13 @@ func (gc *GetCapabilities) Validate(c capabilities.Contents) ows.Exceptions { } // ParseXML builds a GetCapabilities object based on a XML document -func (gc *GetCapabilities) ParseXML(body []byte) ows.Exception { +func (gc *GetCapabilities) ParseXML(body []byte) ows.Exceptions { var xmlattributes ows.XMLAttribute if err := xml.Unmarshal(body, &xmlattributes); err != nil { - return ows.MissingParameterValue() + return ows.Exceptions{ows.MissingParameterValue()} } if err := xml.Unmarshal(body, &gc); err != nil { - return ows.MissingParameterValue("REQUEST") + return ows.Exceptions{ows.MissingParameterValue("REQUEST")} } var n []xml.Attr for _, a := range xmlattributes { @@ -62,7 +62,7 @@ func (gc *GetCapabilities) ParseXML(body []byte) ows.Exception { } // ParseKVP builds a GetCapabilities object based on the available query parameters -func (gc *GetCapabilities) ParseKVP(query url.Values) ows.Exception { +func (gc *GetCapabilities) ParseKVP(query url.Values) ows.Exceptions { for k, v := range query { switch strings.ToUpper(k) { case REQUEST: From 635bff7804774c22b29bedbe7550fb28aa42d424 Mon Sep 17 00:00:00 2001 From: Wouter Date: Tue, 4 Aug 2020 21:45:06 +0200 Subject: [PATCH 27/45] init getfeatureinfokvp, TODO: test need to handle mulitple expections --- pkg/wms130/request/common.go | 6 + pkg/wms130/request/getfeatureinfo.go | 151 +++++++++------------- pkg/wms130/request/getfeatureinfo_test.go | 10 +- pkg/wms130/request/getfeatureinfokvp.go | 115 ++++++++++++++++ pkg/wms130/request/getmapkvp.go | 40 +++--- 5 files changed, 213 insertions(+), 109 deletions(-) create mode 100644 pkg/wms130/request/getfeatureinfokvp.go diff --git a/pkg/wms130/request/common.go b/pkg/wms130/request/common.go index 4c962df..eea7d40 100644 --- a/pkg/wms130/request/common.go +++ b/pkg/wms130/request/common.go @@ -19,6 +19,12 @@ const ( VERSION = `VERSION` ) +// BaseRequestKVP struct +type BaseRequestKVP struct { + Version string `yaml:"version,omitempty"` + Request string `yaml:"request,omitempty"` +} + // BaseRequest based on the SLD 1.1 spec 'containing' example implementation of a POST WMS 1.3.0 request // http://schemas.opengis.net/sld/1.1//example_getmap.xml // Note: not usable for GetCapabilities request regarding deviation of Optional/Mandatory parameters SERVICE and VERSION diff --git a/pkg/wms130/request/getfeatureinfo.go b/pkg/wms130/request/getfeatureinfo.go index c9ce564..5756c9b 100644 --- a/pkg/wms130/request/getfeatureinfo.go +++ b/pkg/wms130/request/getfeatureinfo.go @@ -8,7 +8,6 @@ import ( "strings" "github.com/pdok/ogc-specifications/pkg/ows" - "github.com/pdok/ogc-specifications/pkg/utils" "github.com/pdok/ogc-specifications/pkg/wms130/capabilities" "github.com/pdok/ogc-specifications/pkg/wms130/exception" ) @@ -70,105 +69,81 @@ func (gfi *GetFeatureInfo) ParseXML(body []byte) ows.Exceptions { return nil } -// ParseKVP builds a GetFeatureInfo object based on the available query parameters -func (gfi *GetFeatureInfo) ParseKVP(query url.Values) ows.Exceptions { - if len(query) == 0 { - // When there are no query value we know that at least - // the manadorty VERSION parameter is missing. - return ows.Exceptions{ows.MissingParameterValue(VERSION)} +// ParseGetFeatureInfoKVP process the simple struct to a complex struct +func (gfi *GetFeatureInfo) ParseGetFeatureInfoKVP(gfikvp GetFeatureInfoKVP) ows.Exceptions { + + // Base + gfi.BaseRequest.Build(gfikvp.Service, gfikvp.Version) + + sld, ex := gfikvp.BuildStyledLayerDescriptor() + if ex != nil { + return ows.Exceptions{ex} } + gfi.StyledLayerDescriptor = sld - q := utils.KeysToUpper(query) + gfi.CRS = gfikvp.CRS - // Base - if len(q[REQUEST]) > 0 { - gfi.XMLName.Local = q[REQUEST][0] + var bbox ows.BoundingBox + if err := bbox.Build(gfikvp.Bbox); err != nil { + return ows.Exceptions{err} } + gfi.BoundingBox = bbox - var br BaseRequest - if err := br.ParseKVP(q); err != nil { - return err + gfi.CRS = gfikvp.CRS + + w, err := strconv.Atoi(gfikvp.Width) + if err != nil { + return ows.Exceptions{ows.MissingParameterValue(WIDTH, gfikvp.Width)} } - gfi.BaseRequest = br + gfi.Size.Width = w - var styles, layers []string + h, err := strconv.Atoi(gfikvp.Height) + if err != nil { + return ows.Exceptions{ows.MissingParameterValue(WIDTH, gfikvp.Height)} + } + gfi.Size.Height = h - // GetFeatureInfo mandatory parameters - for _, k := range getFeatureInfoMandatoryParameters { - if len(query[k]) > 0 { - switch k { - case LAYERS: - layers = strings.Split(query[k][0], ",") - case STYLES: - styles = strings.Split(query[k][0], ",") - case CRS: - gfi.CRS = query[k][0] - case BBOX: - var bbox ows.BoundingBox - if err := bbox.Build(query[k][0]); err != nil { - return ows.Exceptions{err} - } - gfi.BoundingBox = bbox - case WIDTH: - i, err := strconv.Atoi(query[k][0]) - if err != nil { - return ows.Exceptions{ows.MissingParameterValue(WIDTH, query[k][0])} - } - gfi.Size.Width = i - case HEIGHT: - i, err := strconv.Atoi(query[k][0]) - if err != nil { - // TODO: ignore or a exception - return ows.Exceptions{ows.MissingParameterValue(HEIGHT, query[k][0])} - } - gfi.Size.Height = i - case QUERYLAYERS: - gfi.QueryLayers = strings.Split(query[k][0], ",") - case I: - i, err := strconv.Atoi(query[k][0]) - if err != nil { - return ows.Exceptions{exception.InvalidPoint(query[I][0], query[J][0])} - } - gfi.I = i - case J: - j, err := strconv.Atoi(query[k][0]) - if err != nil { - return ows.Exceptions{exception.InvalidPoint(query[I][0], query[J][0])} - } - gfi.J = j - } - } + gfi.QueryLayers = strings.Split(gfikvp.QueryLayers, ",") + + i, err := strconv.Atoi(gfikvp.I) + if err != nil { + return ows.Exceptions{exception.InvalidPoint(gfikvp.I, gfikvp.J)} } + gfi.I = i - sld, err := buildStyledLayerDescriptor(layers, styles) - if err == nil { - gfi.StyledLayerDescriptor = sld - } else { - return ows.Exceptions{err} + j, err := strconv.Atoi(gfikvp.J) + if err != nil { + return ows.Exceptions{exception.InvalidPoint(gfikvp.I, gfikvp.J)} } + gfi.J = j - // GetFeatureInfo optional parameters - for _, k := range getFeatureInfoOptionalParameters { - if len(query[k]) > 0 { - switch k { - case INFOFORMAT: - gfi.InfoFormat = &query[k][0] - case FEATURECOUNT: - i, err := strconv.Atoi(query[k][0]) - if err != nil { - // TODO: ignore or a exception - } - gfi.FeatureCount = &i - case EXCEPTIONS: - gfi.Exceptions = &query[k][0] - // case TIME: - // No Time implementation (for now...) - // Time format in ccyy-mm-ddThh:mm:ss.sssZ but also need support for time ranges - // see: OGC 06-042 (WMS 1.3.0 spec) - // case ELEVATION: - // skip for now, same 'issue' as with the TIME - } - } + fc, err := strconv.Atoi(*gfikvp.FeatureCount) + if err != nil { + // TODO: ignore or a exception + } + + gfi.FeatureCount = &fc + gfi.InfoFormat = &gfikvp.InfoFormat + gfi.Exceptions = gfikvp.Exceptions + + return nil +} + +// ParseKVP builds a GetFeatureInfo object based on the available query parameters +func (gfi *GetFeatureInfo) ParseKVP(query url.Values) ows.Exceptions { + if len(query) == 0 { + // When there are no query value we know that at least + // the manadorty VERSION parameter is missing. + return ows.Exceptions{ows.MissingParameterValue(VERSION)} + } + + gfikvp := GetFeatureInfoKVP{} + if err := gfikvp.ParseKVP(query); err != nil { + return err + } + + if err := gfi.ParseGetFeatureInfoKVP(gfikvp); err != nil { + return err } return nil diff --git a/pkg/wms130/request/getfeatureinfo_test.go b/pkg/wms130/request/getfeatureinfo_test.go index 7ffee76..f368bb5 100644 --- a/pkg/wms130/request/getfeatureinfo_test.go +++ b/pkg/wms130/request/getfeatureinfo_test.go @@ -171,7 +171,7 @@ func TestGetFeatureInfoParseKVP(t *testing.T) { Excepted GetFeatureInfo Error ows.Exception }{ - 0: {Query: map[string][]string{REQUEST: {getfeatureinfo}, SERVICE: {Service}, VERSION: {Version}}, Excepted: GetFeatureInfo{XMLName: xml.Name{Local: getfeatureinfo}, BaseRequest: BaseRequest{Version: Version, Service: Service}}}, + 0: {Query: map[string][]string{REQUEST: {getfeatureinfo}, SERVICE: {Service}, VERSION: {Version}}, Error: ows.InvalidParameterValue("", `boundingbox`)}, 1: {Query: url.Values{}, Error: ows.MissingParameterValue(VERSION)}, 2: {Query: map[string][]string{REQUEST: {getmap}, SERVICE: {Service}, VERSION: {Version}, LAYERS: {`Rivers,Roads,Houses`}, @@ -212,10 +212,10 @@ func TestGetFeatureInfoParseKVP(t *testing.T) { InfoFormat: sp(`application/json`), }, }, - 3: {Query: map[string][]string{WIDTH: {`not a number`}, VERSION: {Version}}, Error: ows.MissingParameterValue(WIDTH, `not a number`)}, - 4: {Query: map[string][]string{HEIGHT: {`not a number`}, VERSION: {Version}}, Error: ows.MissingParameterValue(HEIGHT, `not a number`)}, - 5: {Query: map[string][]string{I: {`not a number`}, J: {`1`}, VERSION: {Version}}, Error: exception.InvalidPoint(`not a number`, `1`)}, - 6: {Query: map[string][]string{J: {`not a number`}, I: {`1`}, VERSION: {Version}}, Error: exception.InvalidPoint(`1`, `not a number`)}, + 3: {Query: map[string][]string{WIDTH: {`not a number`}, VERSION: {Version}, BBOX: {`-180.0,-90.0,180.0,90.0`}}, Error: ows.MissingParameterValue(WIDTH, `not a number`)}, + 4: {Query: map[string][]string{WIDTH: {`1024`}, HEIGHT: {`not a number`}, VERSION: {Version}, BBOX: {`-180.0,-90.0,180.0,90.0`}}, Error: ows.MissingParameterValue(HEIGHT, `not a number`)}, + 5: {Query: map[string][]string{I: {`not a number`}, J: {`1`}, VERSION: {Version}, BBOX: {`-180.0,-90.0,180.0,90.0`}}, Error: exception.InvalidPoint(`not a number`, `1`)}, + 6: {Query: map[string][]string{J: {`not a number`}, I: {`1`}, VERSION: {Version}, BBOX: {`-180.0,-90.0,180.0,90.0`}}, Error: exception.InvalidPoint(`1`, `not a number`)}, } for k, n := range tests { var gfi GetFeatureInfo diff --git a/pkg/wms130/request/getfeatureinfokvp.go b/pkg/wms130/request/getfeatureinfokvp.go new file mode 100644 index 0000000..111203b --- /dev/null +++ b/pkg/wms130/request/getfeatureinfokvp.go @@ -0,0 +1,115 @@ +package request + +import ( + "net/url" + "strings" + + "github.com/pdok/ogc-specifications/pkg/ows" +) + +//GetFeatureInfoKVP struct +type GetFeatureInfoKVP struct { + // Table 8 - The Parameters of a GetFeatureInfo request + Service string `yaml:"service,omitempty"` + BaseRequestKVP + GetMapKVPMandatory + GetFeatureInfoKVPMandatory + GetFeatureInfoKVPOptional +} + +// GetFeatureInfoKVPMandatory struct containing the mandatory WMS request KVP +type GetFeatureInfoKVPMandatory struct { + QueryLayers string `yaml:"query_layers,omitempty"` + InfoFormat string `yaml:"info_format,omitempty"` + I string `yaml:"i,omitempty"` + J string `yaml:"j,omitempty"` +} + +// GetFeatureInfoKVPOptional struct containing the optional WMS request KVP +type GetFeatureInfoKVPOptional struct { + FeatureCount *string `yaml:"feature_count,omitempty"` + Exceptions *string `yaml:"exceptions,omitempty"` +} + +// ParseKVP builds a GetMapKVP object based on the available query parameters +func (gfikvp *GetFeatureInfoKVP) ParseKVP(query url.Values) ows.Exceptions { + var exceptions ows.Exceptions + for k, v := range query { + if len(v) != 1 { + exceptions = append(exceptions, ows.InvalidParameterValue(k, strings.Join(v, ","))) + } else { + switch k { + case SERVICE: + gfikvp.Service = v[0] + case VERSION: + gfikvp.BaseRequestKVP.Version = v[0] + case REQUEST: + gfikvp.BaseRequestKVP.Request = v[0] + case LAYERS: + gfikvp.GetMapKVPMandatory.Layers = v[0] + case STYLES: + gfikvp.GetMapKVPMandatory.Styles = v[0] + case CRS: + gfikvp.GetMapKVPMandatory.CRS = v[0] + case BBOX: + gfikvp.GetMapKVPMandatory.Bbox = v[0] + case WIDTH: + gfikvp.GetMapKVPMandatory.Width = v[0] + case HEIGHT: + gfikvp.GetMapKVPMandatory.Height = v[0] + case FORMAT: + gfikvp.GetMapKVPMandatory.Format = v[0] + case QUERYLAYERS: + gfikvp.GetFeatureInfoKVPMandatory.QueryLayers = v[0] + case INFOFORMAT: + gfikvp.GetFeatureInfoKVPMandatory.InfoFormat = v[0] + case I: + gfikvp.GetFeatureInfoKVPMandatory.I = v[0] + case J: + gfikvp.GetFeatureInfoKVPMandatory.J = v[0] + case FEATURECOUNT: + vp := v[0] + gfikvp.GetFeatureInfoKVPOptional.FeatureCount = &vp + case EXCEPTIONS: + vp := v[0] + gfikvp.GetFeatureInfoKVPOptional.Exceptions = &vp + } + } + } + + if len(exceptions) > 0 { + return exceptions + } + + return nil +} + +// BuildKVP builds a url.Values query from a GetMapKVP struct +func (gfikvp *GetFeatureInfoKVP) BuildKVP() url.Values { + query := make(map[string][]string) + + query[SERVICE] = []string{gfikvp.Service} + query[VERSION] = []string{gfikvp.Version} + query[REQUEST] = []string{gfikvp.Request} + query[LAYERS] = []string{gfikvp.Layers} + query[STYLES] = []string{gfikvp.Styles} + query[CRS] = []string{gfikvp.CRS} + query[BBOX] = []string{gfikvp.Bbox} + query[WIDTH] = []string{gfikvp.Width} + query[HEIGHT] = []string{gfikvp.Height} + query[FORMAT] = []string{gfikvp.Format} + + query[QUERYLAYERS] = []string{gfikvp.QueryLayers} + query[INFOFORMAT] = []string{gfikvp.InfoFormat} + query[I] = []string{gfikvp.I} + query[J] = []string{gfikvp.J} + + if gfikvp.FeatureCount != nil { + query[FEATURECOUNT] = []string{*gfikvp.FeatureCount} + } + if gfikvp.Exceptions != nil { + query[EXCEPTIONS] = []string{*gfikvp.Exceptions} + } + + return query +} diff --git a/pkg/wms130/request/getmapkvp.go b/pkg/wms130/request/getmapkvp.go index 7b77e29..1c22ade 100644 --- a/pkg/wms130/request/getmapkvp.go +++ b/pkg/wms130/request/getmapkvp.go @@ -12,21 +12,25 @@ import ( type GetMapKVP struct { // Table 8 - The Parameters of a GetMap request Service string `yaml:"service,omitempty"` + BaseRequestKVP GetMapKVPMandatory GetMapKVPOptional } +// StyledLayer struct +type StyledLayer struct { + Layers string `yaml:"layers,omitempty"` + Styles string `yaml:"styles,omitempty"` +} + // GetMapKVPMandatory struct containing the mandatory WMS request KVP type GetMapKVPMandatory struct { - Version string `yaml:"version,omitempty"` - Request string `yaml:"request,omitempty"` - Layers string `yaml:"layers,omitempty"` - Styles string `yaml:"styles,omitempty"` - CRS string `yaml:"crs,omitempty"` - Bbox string `yaml:"bbox,omitempty"` - Width string `yaml:"width,omitempty"` - Height string `yaml:"height,omitempty"` - Format string `yaml:"format,omitempty"` + StyledLayer + CRS string `yaml:"crs,omitempty"` + Bbox string `yaml:"bbox,omitempty"` + Width string `yaml:"width,omitempty"` + Height string `yaml:"height,omitempty"` + Format string `yaml:"format,omitempty"` } // GetMapKVPOptional struct containing the optional WMS request KVP @@ -50,9 +54,9 @@ func (gmkvp *GetMapKVP) ParseKVP(query url.Values) ows.Exceptions { case SERVICE: gmkvp.Service = v[0] case VERSION: - gmkvp.GetMapKVPMandatory.Version = v[0] + gmkvp.BaseRequestKVP.Version = v[0] case REQUEST: - gmkvp.GetMapKVPMandatory.Request = v[0] + gmkvp.BaseRequestKVP.Request = v[0] case LAYERS: gmkvp.GetMapKVPMandatory.Layers = v[0] case STYLES: @@ -80,6 +84,10 @@ func (gmkvp *GetMapKVP) ParseKVP(query url.Values) ows.Exceptions { } } + if len(exceptions) > 0 { + return exceptions + } + return nil } @@ -139,13 +147,13 @@ func (gmkvp *GetMapKVP) BuildOutput() (Output, ows.Exception) { } // BuildStyledLayerDescriptor builds a StyledLayerDescriptor struct from the KVP information -func (gmkvp *GetMapKVP) BuildStyledLayerDescriptor() (StyledLayerDescriptor, ows.Exception) { +func (sl *StyledLayer) BuildStyledLayerDescriptor() (StyledLayerDescriptor, ows.Exception) { var layers, styles []string - if gmkvp.Layers != `` { - layers = strings.Split(gmkvp.Layers, ",") + if sl.Layers != `` { + layers = strings.Split(sl.Layers, ",") } - if gmkvp.Styles != `` { - styles = strings.Split(gmkvp.Styles, ",") + if sl.Styles != `` { + styles = strings.Split(sl.Styles, ",") } sld, err := buildStyledLayerDescriptor(layers, styles) From c65992d4a7c3d7cc6c96756b44f9dd09911587ec Mon Sep 17 00:00:00 2001 From: Wouter Date: Wed, 5 Aug 2020 09:04:26 +0200 Subject: [PATCH 28/45] updated TestGetFeatureInfoParseKVP test and formatting InvalidPoint exception message --- pkg/wms130/exception/exception.go | 2 +- pkg/wms130/exception/exception_test.go | 4 +-- pkg/wms130/request/getfeatureinfo.go | 2 +- pkg/wms130/request/getfeatureinfo_test.go | 44 +++++++++++++++-------- 4 files changed, 33 insertions(+), 19 deletions(-) diff --git a/pkg/wms130/exception/exception.go b/pkg/wms130/exception/exception.go index f8a77d0..51ce2ba 100644 --- a/pkg/wms130/exception/exception.go +++ b/pkg/wms130/exception/exception.go @@ -126,7 +126,7 @@ func LayerNotQueryable(s ...string) WMSException { func InvalidPoint(i, j string) WMSException { // TODO provide giving WIDTH and HEIGHT values in Exception response return WMSException{ - ExceptionText: fmt.Sprintf("The parameters I and J are invalid, given: %s, %s", i, j), + ExceptionText: fmt.Sprintf("The parameters I and J are invalid, given: %s for I and %s for J", i, j), ExceptionCode: `InvalidPoint`, } } diff --git a/pkg/wms130/exception/exception_test.go b/pkg/wms130/exception/exception_test.go index 7e41479..155cfd0 100644 --- a/pkg/wms130/exception/exception_test.go +++ b/pkg/wms130/exception/exception_test.go @@ -38,7 +38,7 @@ func TestWFSException(t *testing.T) { }, 6: {exception: InvalidPoint("0", "0"), exceptionCode: "InvalidPoint", - exceptionText: "The parameters I and J are invalid, given: 0, 0", + exceptionText: "The parameters I and J are invalid, given: 0 for I and 0 for J", }, 7: {exception: CurrentUpdateSequence(), exceptionCode: "CurrentUpdateSequence", @@ -93,7 +93,7 @@ func TestReport(t *testing.T) { result: []byte(` Layer: unknown:layer, can not be queried - The parameters I and J are invalid, given: 0, 0 + The parameters I and J are invalid, given: 0 for I and 0 for J `)}, } diff --git a/pkg/wms130/request/getfeatureinfo.go b/pkg/wms130/request/getfeatureinfo.go index 5756c9b..c364774 100644 --- a/pkg/wms130/request/getfeatureinfo.go +++ b/pkg/wms130/request/getfeatureinfo.go @@ -99,7 +99,7 @@ func (gfi *GetFeatureInfo) ParseGetFeatureInfoKVP(gfikvp GetFeatureInfoKVP) ows. h, err := strconv.Atoi(gfikvp.Height) if err != nil { - return ows.Exceptions{ows.MissingParameterValue(WIDTH, gfikvp.Height)} + return ows.Exceptions{ows.MissingParameterValue(HEIGHT, gfikvp.Height)} } gfi.Size.Height = h diff --git a/pkg/wms130/request/getfeatureinfo_test.go b/pkg/wms130/request/getfeatureinfo_test.go index f368bb5..a7d3053 100644 --- a/pkg/wms130/request/getfeatureinfo_test.go +++ b/pkg/wms130/request/getfeatureinfo_test.go @@ -167,12 +167,12 @@ func TestGetFeatureInfoBuildXML(t *testing.T) { func TestGetFeatureInfoParseKVP(t *testing.T) { var tests = []struct { - Query url.Values - Excepted GetFeatureInfo - Error ows.Exception + Query url.Values + Excepted GetFeatureInfo + Exceptions ows.Exceptions }{ - 0: {Query: map[string][]string{REQUEST: {getfeatureinfo}, SERVICE: {Service}, VERSION: {Version}}, Error: ows.InvalidParameterValue("", `boundingbox`)}, - 1: {Query: url.Values{}, Error: ows.MissingParameterValue(VERSION)}, + 0: {Query: map[string][]string{REQUEST: {getfeatureinfo}, SERVICE: {Service}, VERSION: {Version}}, Exceptions: ows.Exceptions{ows.InvalidParameterValue("", `boundingbox`)}}, + 1: {Query: url.Values{}, Exceptions: ows.Exceptions{ows.MissingParameterValue(VERSION)}}, 2: {Query: map[string][]string{REQUEST: {getmap}, SERVICE: {Service}, VERSION: {Version}, LAYERS: {`Rivers,Roads,Houses`}, STYLES: {`CenterLine,,Outline`}, @@ -212,20 +212,34 @@ func TestGetFeatureInfoParseKVP(t *testing.T) { InfoFormat: sp(`application/json`), }, }, - 3: {Query: map[string][]string{WIDTH: {`not a number`}, VERSION: {Version}, BBOX: {`-180.0,-90.0,180.0,90.0`}}, Error: ows.MissingParameterValue(WIDTH, `not a number`)}, - 4: {Query: map[string][]string{WIDTH: {`1024`}, HEIGHT: {`not a number`}, VERSION: {Version}, BBOX: {`-180.0,-90.0,180.0,90.0`}}, Error: ows.MissingParameterValue(HEIGHT, `not a number`)}, - 5: {Query: map[string][]string{I: {`not a number`}, J: {`1`}, VERSION: {Version}, BBOX: {`-180.0,-90.0,180.0,90.0`}}, Error: exception.InvalidPoint(`not a number`, `1`)}, - 6: {Query: map[string][]string{J: {`not a number`}, I: {`1`}, VERSION: {Version}, BBOX: {`-180.0,-90.0,180.0,90.0`}}, Error: exception.InvalidPoint(`1`, `not a number`)}, + 3: {Query: map[string][]string{WIDTH: {`not a number`}, VERSION: {Version}, BBOX: {`-180.0,-90.0,180.0,90.0`}}, Exceptions: ows.Exceptions{ows.MissingParameterValue(WIDTH, `not a number`)}}, + 4: {Query: map[string][]string{WIDTH: {`1024`}, HEIGHT: {`not a number`}, VERSION: {Version}, BBOX: {`-180.0,-90.0,180.0,90.0`}}, Exceptions: ows.Exceptions{ows.MissingParameterValue(HEIGHT, `not a number`)}}, + 5: {Query: map[string][]string{WIDTH: {`1024`}, HEIGHT: {`1024`}, I: {`not a number`}, J: {`1`}, VERSION: {Version}, BBOX: {`-180.0,-90.0,180.0,90.0`}}, Exceptions: ows.Exceptions{exception.InvalidPoint(`not a number`, `1`)}}, + 6: {Query: map[string][]string{WIDTH: {`1024`}, HEIGHT: {`1024`}, I: {`1`}, J: {`not a number`}, VERSION: {Version}, BBOX: {`-180.0,-90.0,180.0,90.0`}}, Exceptions: ows.Exceptions{exception.InvalidPoint(`1`, `not a number`)}}, + 7: {Query: map[string][]string{WIDTH: {`1024`}, HEIGHT: {`1024`}, I: {`this in not a number`}, J: {`this is also not a number`}, VERSION: {Version}, BBOX: {`-180.0,-90.0,180.0,90.0`}}, Exceptions: ows.Exceptions{exception.InvalidPoint(`this in not a number`, `this is also not a number`)}}, } - for k, n := range tests { + + for k, test := range tests { var gfi GetFeatureInfo - err := gfi.ParseKVP(n.Query) - if err != nil { - if err[0].Error() != n.Error.Error() { - t.Errorf("test: %d, expected: %s,\n got: %s", k, n.Error, err) + errs := gfi.ParseKVP(test.Query) + if errs != nil { + if len(errs) != len(test.Exceptions) { + t.Errorf("test: %d, expected: %d exceptions,\n got: %d exceptions", k, len(test.Exceptions), len(errs)) + } else { + for _, exception := range errs { + found := false + for _, expectedeexception := range test.Exceptions { + if expectedeexception == exception { + found = true + } + } + if !found { + t.Errorf("test: %d, expected one of: %s,\n got: %s", k, test.Exceptions, exception) + } + } } } else { - compareGetFeatureInfoObject(gfi, n.Excepted, t, k) + compareGetFeatureInfoObject(gfi, test.Excepted, t, k) } } } From 00833210908c5d57795057538a380a5df53de9fd Mon Sep 17 00:00:00 2001 From: Wouter Date: Wed, 5 Aug 2020 09:27:11 +0200 Subject: [PATCH 29/45] added OperationRequestKVP interface --- images/layout.drawio | 79 +++++++++++++++++++++++++------------------- 1 file changed, 45 insertions(+), 34 deletions(-) diff --git a/images/layout.drawio b/images/layout.drawio index 4d3822a..76392c3 100644 --- a/images/layout.drawio +++ b/images/layout.drawio @@ -1,67 +1,78 @@ - + - + - + - + - - + + - - + + - - + + - - + + + + + + + + + + + + + - + - + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + From cb1f7425d87866a335830e275e24893cae2313ce Mon Sep 17 00:00:00 2001 From: Wouter Date: Wed, 5 Aug 2020 10:33:23 +0200 Subject: [PATCH 30/45] renamed file operationkvprequest->operationrequestkvp --- pkg/ows/operationkvprequest.go | 12 ------------ pkg/ows/operationrequestkvp.go | 15 +++++++++++++++ 2 files changed, 15 insertions(+), 12 deletions(-) delete mode 100644 pkg/ows/operationkvprequest.go create mode 100644 pkg/ows/operationrequestkvp.go diff --git a/pkg/ows/operationkvprequest.go b/pkg/ows/operationkvprequest.go deleted file mode 100644 index 953dde4..0000000 --- a/pkg/ows/operationkvprequest.go +++ /dev/null @@ -1,12 +0,0 @@ -package ows - -import "net/url" - -// OperationKVPRequest interface -type OperationKVPRequest interface { - Type() string - Validate(Capability) Exceptions - - ParseKVP(url.Values) Exceptions - BuildKVP() url.Values -} diff --git a/pkg/ows/operationrequestkvp.go b/pkg/ows/operationrequestkvp.go new file mode 100644 index 0000000..9b0b74a --- /dev/null +++ b/pkg/ows/operationrequestkvp.go @@ -0,0 +1,15 @@ +package ows + +import ( + "net/url" +) + +// OperationRequestKVP interface +type OperationRequestKVP interface { + Type() string + Validate(Capability) Exceptions + + ParseKVP(url.Values) Exceptions + ParseOperationsRequest(OperationRequest) Exceptions + BuildKVP() url.Values +} From 5bf955c44c20569e86b3ab409c4be2950e9a7648 Mon Sep 17 00:00:00 2001 From: Wouter Date: Wed, 5 Aug 2020 10:34:37 +0200 Subject: [PATCH 31/45] update capabilities interface + fixes getfeatureinfokvp --- pkg/wcs201/capabilities/capabilities.go | 4 +- pkg/wfs200/capabilities/capabilities.go | 4 +- pkg/wfs200/request/getfeature.go | 5 +- pkg/wms130/capabilities/capabilities.go | 6 +-- pkg/wms130/request/getfeatureinfo.go | 68 +++++------------------- pkg/wms130/request/getfeatureinfokvp.go | 35 +++++++++++- pkg/wms130/request/getmap.go | 16 +++--- pkg/wms130/request/getmapkvp.go | 4 +- pkg/wmts100/capabilities/capabilities.go | 4 +- 9 files changed, 69 insertions(+), 77 deletions(-) diff --git a/pkg/wcs201/capabilities/capabilities.go b/pkg/wcs201/capabilities/capabilities.go index 26c6303..c16ba4c 100644 --- a/pkg/wcs201/capabilities/capabilities.go +++ b/pkg/wcs201/capabilities/capabilities.go @@ -5,8 +5,8 @@ func (c *Capability) ParseXML(doc []byte) error { return nil } -// ParseYML func -func (c *Capability) ParseYML(doc []byte) error { +// ParseYAMl func +func (c *Capability) ParseYAMl(doc []byte) error { return nil } diff --git a/pkg/wfs200/capabilities/capabilities.go b/pkg/wfs200/capabilities/capabilities.go index 4e0d1cb..d9f3550 100644 --- a/pkg/wfs200/capabilities/capabilities.go +++ b/pkg/wfs200/capabilities/capabilities.go @@ -11,8 +11,8 @@ func (c *Capability) ParseXML(doc []byte) error { return nil } -// ParseYML func -func (c *Capability) ParseYML(doc []byte) error { +// ParseYAMl func +func (c *Capability) ParseYAMl(doc []byte) error { return nil } diff --git a/pkg/wfs200/request/getfeature.go b/pkg/wfs200/request/getfeature.go index c7603c0..3e54ec7 100644 --- a/pkg/wfs200/request/getfeature.go +++ b/pkg/wfs200/request/getfeature.go @@ -10,7 +10,6 @@ import ( "github.com/pdok/ogc-specifications/pkg/ows" "github.com/pdok/ogc-specifications/pkg/utils" - "github.com/pdok/ogc-specifications/pkg/wfs200/capabilities" "github.com/pdok/ogc-specifications/pkg/wfs200/exception" ) @@ -49,7 +48,9 @@ func (gf *GetFeature) Type() string { } // Validate returns GetFeature -func (gf *GetFeature) Validate(c capabilities.Capability) ows.Exceptions { +func (gf *GetFeature) Validate(c ows.Capability) ows.Exceptions { + + //getfeaturecap := c.(capabilities.Capability) return nil } diff --git a/pkg/wms130/capabilities/capabilities.go b/pkg/wms130/capabilities/capabilities.go index 73b5c0a..9a77a8d 100644 --- a/pkg/wms130/capabilities/capabilities.go +++ b/pkg/wms130/capabilities/capabilities.go @@ -3,12 +3,12 @@ package capabilities import "github.com/pdok/ogc-specifications/pkg/ows" // ParseXML func -func (c *Capability) ParseXML(doc []byte) error { +func (c Capability) ParseXML(doc []byte) error { return nil } -// ParseYML func -func (c *Capability) ParseYML(doc []byte) error { +// ParseYAMl func +func (c Capability) ParseYAMl(doc []byte) error { return nil } diff --git a/pkg/wms130/request/getfeatureinfo.go b/pkg/wms130/request/getfeatureinfo.go index c364774..61c6af6 100644 --- a/pkg/wms130/request/getfeatureinfo.go +++ b/pkg/wms130/request/getfeatureinfo.go @@ -26,12 +26,12 @@ const ( FEATURECOUNT = `FEATURE_COUNT` ) -var getFeatureInfoMandatoryParameters, getFeatureInfoOptionalParameters []string +// var getFeatureInfoMandatoryParameters, getFeatureInfoOptionalParameters []string -func init() { - getFeatureInfoMandatoryParameters = []string{LAYERS, STYLES, CRS, BBOX, WIDTH, HEIGHT, FORMAT, QUERYLAYERS, I, J} - getFeatureInfoOptionalParameters = []string{TRANSPARENT, BGCOLOR, EXCEPTIONS, INFOFORMAT, FEATURECOUNT} -} +// func init() { +// getFeatureInfoMandatoryParameters = []string{LAYERS, STYLES, CRS, BBOX, WIDTH, HEIGHT, FORMAT, QUERYLAYERS, I, J} +// getFeatureInfoOptionalParameters = []string{TRANSPARENT, BGCOLOR, EXCEPTIONS, INFOFORMAT, FEATURECOUNT} +// } // Type returns GetFeatureInfo func (gfi *GetFeatureInfo) Type() string { @@ -151,54 +151,11 @@ func (gfi *GetFeatureInfo) ParseKVP(query url.Values) ows.Exceptions { // BuildKVP builds a new query string that will be proxied func (gfi *GetFeatureInfo) BuildKVP() url.Values { - querystring := make(map[string][]string) - - // base - querystring[REQUEST] = []string{gfi.XMLName.Local} - querystring[SERVICE] = []string{gfi.BaseRequest.Service} - querystring[VERSION] = []string{gfi.BaseRequest.Version} - - for _, k := range getFeatureInfoMandatoryParameters { - switch k { - case LAYERS: - querystring[LAYERS] = []string{gfi.StyledLayerDescriptor.getLayerKVPValue()} - case STYLES: - querystring[STYLES] = []string{gfi.StyledLayerDescriptor.getStyleKVPValue()} - case CRS: - querystring[CRS] = []string{gfi.CRS} - case BBOX: - querystring[BBOX] = []string{gfi.BoundingBox.BuildKVP()} - case WIDTH: - querystring[WIDTH] = []string{strconv.Itoa(gfi.Size.Width)} - case HEIGHT: - querystring[HEIGHT] = []string{strconv.Itoa(gfi.Size.Height)} - case QUERYLAYERS: - querystring[QUERYLAYERS] = []string{strings.Join(gfi.QueryLayers, ",")} - case I: - querystring[I] = []string{strconv.Itoa(gfi.I)} - case J: - querystring[J] = []string{strconv.Itoa(gfi.J)} - } - } - - for _, k := range getFeatureInfoOptionalParameters { - switch k { - case INFOFORMAT: - if gfi.InfoFormat != nil { - querystring[INFOFORMAT] = []string{*gfi.InfoFormat} - } - case FEATURECOUNT: - if gfi.FeatureCount != nil { - querystring[FEATURECOUNT] = []string{strconv.Itoa(*gfi.FeatureCount)} - } - case EXCEPTIONS: - if gfi.Exceptions != nil { - querystring[EXCEPTIONS] = []string{*gfi.Exceptions} - } - } - } + gfikvp := GetFeatureInfoKVP{} + gfikvp.ParseOperationsRequest(gfi) - return querystring + kvp := gfikvp.BuildKVP() + return kvp } // BuildXML builds a 'new' XML document 'based' on the 'original' XML document @@ -219,16 +176,17 @@ type GetFeatureInfo struct { // // These are the 'minimum' required GetMap parameters // needed in a GetFeatureInfo request - StyledLayerDescriptor StyledLayerDescriptor `xml:"StyledLayerDescriptor" yaml:"styledlayerdescriptor"` + StyledLayerDescriptor StyledLayerDescriptor `xml:"StyledLayerDescriptor" yaml:"styledlayerdescriptor"` //TODO layers is need styles is not! CRS string `xml:"CRS" yaml:"crs"` BoundingBox ows.BoundingBox `xml:"BoundingBox" yaml:"boundingbox"` // We skip the Output struct, because these are not required parameters - Size Size `xml:"Size" yaml:"size"` + Size Size `xml:"Size" yaml:"size"` + Format string `xml:"Format,omitempty" yaml:"format,omitempty"` QueryLayers []string `xml:"QueryLayers" yaml:"querylayers"` I int `xml:"I" yaml:"i"` J int `xml:"J" yaml:"j"` InfoFormat *string `xml:"InfoFormat" yaml:"infoformat"` - FeatureCount *int `xml:"FeatureCount" yaml:"featurecount"` + FeatureCount *int `xml:"FeatureCount,omitempty" yaml:"featurecount,omitempty"` Exceptions *string `xml:"Exceptions" yaml:"exceptions"` } diff --git a/pkg/wms130/request/getfeatureinfokvp.go b/pkg/wms130/request/getfeatureinfokvp.go index 111203b..1b7d34a 100644 --- a/pkg/wms130/request/getfeatureinfokvp.go +++ b/pkg/wms130/request/getfeatureinfokvp.go @@ -2,6 +2,7 @@ package request import ( "net/url" + "strconv" "strings" "github.com/pdok/ogc-specifications/pkg/ows" @@ -97,7 +98,10 @@ func (gfikvp *GetFeatureInfoKVP) BuildKVP() url.Values { query[BBOX] = []string{gfikvp.Bbox} query[WIDTH] = []string{gfikvp.Width} query[HEIGHT] = []string{gfikvp.Height} - query[FORMAT] = []string{gfikvp.Format} + + if gfikvp.Format != `` { + query[FORMAT] = []string{gfikvp.Format} + } query[QUERYLAYERS] = []string{gfikvp.QueryLayers} query[INFOFORMAT] = []string{gfikvp.InfoFormat} @@ -113,3 +117,32 @@ func (gfikvp *GetFeatureInfoKVP) BuildKVP() url.Values { return query } + +// ParseOperationsRequest builds a GetFeatureInfoKVP object based on a GetFeatureInfo struct +func (gfikvp *GetFeatureInfoKVP) ParseOperationsRequest(gfi *GetFeatureInfo) ows.Exceptions { + gfikvp.Request = getfeatureinfo + gfikvp.Version = Version + gfikvp.Service = Service + gfikvp.Layers = gfi.StyledLayerDescriptor.getLayerKVPValue() + gfikvp.Styles = gfi.StyledLayerDescriptor.getStyleKVPValue() + gfikvp.CRS = gfi.CRS + gfikvp.Bbox = gfi.BoundingBox.BuildKVP() + gfikvp.Width = strconv.Itoa(gfi.Size.Width) + gfikvp.Height = strconv.Itoa(gfi.Size.Height) + + gfikvp.QueryLayers = strings.Join(gfi.QueryLayers, ",") + gfikvp.InfoFormat = *gfi.InfoFormat + gfikvp.I = strconv.Itoa(gfi.I) + gfikvp.J = strconv.Itoa(gfi.J) + + gfikvp.Format = gfi.Format + + if gfi.FeatureCount != nil { + fcp := strconv.Itoa(*gfi.FeatureCount) + gfikvp.FeatureCount = &fcp + } + + gfikvp.Exceptions = gfi.Exceptions + + return nil +} diff --git a/pkg/wms130/request/getmap.go b/pkg/wms130/request/getmap.go index f5e21d0..bc5bb99 100644 --- a/pkg/wms130/request/getmap.go +++ b/pkg/wms130/request/getmap.go @@ -40,11 +40,13 @@ func (gm *GetMap) Type() string { } // Validate returns GetMap -func (gm *GetMap) Validate(c capabilities.Capability) ows.Exceptions { +func (gm *GetMap) Validate(c ows.Capability) ows.Exceptions { var exceptions ows.Exceptions - exceptions = append(exceptions, gm.StyledLayerDescriptor.Validate(c)...) - exceptions = append(exceptions, gm.Output.Validate(c)...) + getmapcap := c.(capabilities.Capability) + + exceptions = append(exceptions, gm.StyledLayerDescriptor.Validate(getmapcap)...) + exceptions = append(exceptions, gm.Output.Validate(getmapcap)...) return exceptions } @@ -120,12 +122,10 @@ func (gm *GetMap) ParseXML(body []byte) ows.Exceptions { // BuildKVP builds a new query string that will be proxied func (gm *GetMap) BuildKVP() url.Values { gmkvp := GetMapKVP{} - gmkvp.ParseGetMap(gm) - - query := gmkvp.BuildKVP() - // query := map[string][]string{} + gmkvp.ParseOperationsRequest(gm) - return query + kvp := gmkvp.BuildKVP() + return kvp } // BuildXML builds a 'new' XML document 'based' on the 'original' XML document diff --git a/pkg/wms130/request/getmapkvp.go b/pkg/wms130/request/getmapkvp.go index 1c22ade..77bb10d 100644 --- a/pkg/wms130/request/getmapkvp.go +++ b/pkg/wms130/request/getmapkvp.go @@ -91,8 +91,8 @@ func (gmkvp *GetMapKVP) ParseKVP(query url.Values) ows.Exceptions { return nil } -// ParseGetMap builds a GetMapKVP object based on a GetMap struct -func (gmkvp *GetMapKVP) ParseGetMap(gm *GetMap) ows.Exception { +// ParseOperationsRequest builds a GetMapKVP object based on a GetMap struct +func (gmkvp *GetMapKVP) ParseOperationsRequest(gm *GetMap) ows.Exceptions { gmkvp.Request = getmap gmkvp.Version = Version gmkvp.Service = Service diff --git a/pkg/wmts100/capabilities/capabilities.go b/pkg/wmts100/capabilities/capabilities.go index 54e7ba3..3a1ec7d 100644 --- a/pkg/wmts100/capabilities/capabilities.go +++ b/pkg/wmts100/capabilities/capabilities.go @@ -5,8 +5,8 @@ func (c *Contents) ParseXML(doc []byte) error { return nil } -// ParseYML func -func (c *Contents) ParseYML(doc []byte) error { +// ParseYAMl func +func (c *Contents) ParseYAMl(doc []byte) error { return nil } From 96b33f69642002a56b4b9cb3839d9279c078b878 Mon Sep 17 00:00:00 2001 From: Wouter Date: Wed, 5 Aug 2020 10:47:39 +0200 Subject: [PATCH 32/45] added future TODO's --- pkg/ows/operationresponse.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/ows/operationresponse.go b/pkg/ows/operationresponse.go index 8a04174..2d80e43 100644 --- a/pkg/ows/operationresponse.go +++ b/pkg/ows/operationresponse.go @@ -5,10 +5,10 @@ type OperationResponse interface { Type() string Service() string Version() string - Validate() Exceptions + Validate() Exceptions //TODO No sure about this one // ParseXML([]byte) Exceptions - ParseYAML([]byte) Exception + ParseYAML([]byte) Exception //TODO Maybe just return error BuildXML() []byte BuildYAML() []byte } From 1cfd2c8f1f1b2cb9976fab1a5e22458532dd41e4 Mon Sep 17 00:00:00 2001 From: Wouter Date: Wed, 5 Aug 2020 10:48:05 +0200 Subject: [PATCH 33/45] yaml config novalue->novalues --- pkg/wfs200/capabilities/capabilities.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/wfs200/capabilities/capabilities.go b/pkg/wfs200/capabilities/capabilities.go index d9f3550..3b44fbb 100644 --- a/pkg/wfs200/capabilities/capabilities.go +++ b/pkg/wfs200/capabilities/capabilities.go @@ -47,7 +47,7 @@ type OperationsMetadata struct { type Constraint struct { Text string `xml:",chardata"` Name string `xml:"name,attr" yaml:"name"` - NoValues *string `xml:"ows:NoValues" yaml:"novalue"` + NoValues *string `xml:"ows:NoValues" yaml:"novalues"` DefaultValue *string `xml:"ows:DefaultValue" yaml:"defaultvalue"` AllowedValues *AllowedValues `xml:"ows:AllowedValues" yaml:"allowedvalues"` } From 080aaa731143bc964208be790b8629ca14c16a08 Mon Sep 17 00:00:00 2001 From: Wouter Date: Wed, 5 Aug 2020 10:56:41 +0200 Subject: [PATCH 34/45] typos --- pkg/ows/common.go | 2 +- pkg/ows/common_test.go | 4 ++-- pkg/ows/xml_test.go | 4 ++-- pkg/wms130/request/getfeatureinfo_test.go | 6 +++--- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pkg/ows/common.go b/pkg/ows/common.go index 4d3e10e..2c4af80 100644 --- a/pkg/ows/common.go +++ b/pkg/ows/common.go @@ -65,7 +65,7 @@ func (b *BoundingBox) Build(boundingbox string) Exception { return nil } -// Keywords in struct for repeatablity +// Keywords in struct for repeatability type Keywords struct { Keyword []string `xml:"Keyword" yaml:"keyword"` } diff --git a/pkg/ows/common_test.go b/pkg/ows/common_test.go index e21bd85..3dcc16c 100644 --- a/pkg/ows/common_test.go +++ b/pkg/ows/common_test.go @@ -66,9 +66,9 @@ func TestStripDuplicateAttr(t *testing.T) { t.Errorf("test: %d, expected: %s,\n got: %s", k, a.expected, stripped) } else { c := false - for _, exceptedattr := range a.expected { + for _, exceptedAttr := range a.expected { for _, result := range stripped { - if exceptedattr == result { + if exceptedAttr == result { c = true } } diff --git a/pkg/ows/xml_test.go b/pkg/ows/xml_test.go index 146e51c..1c8cb32 100644 --- a/pkg/ows/xml_test.go +++ b/pkg/ows/xml_test.go @@ -30,9 +30,9 @@ func TestUnMarshalXMLAttribute(t *testing.T) { t.Errorf("test: %d, expected: %s,\n got: %s", k, a.expected, xmlattr) } else { c := false - for _, exceptedattr := range a.expected { + for _, exceptedAttr := range a.expected { for _, result := range xmlattr { - if exceptedattr == result { + if exceptedAttr == result { c = true } } diff --git a/pkg/wms130/request/getfeatureinfo_test.go b/pkg/wms130/request/getfeatureinfo_test.go index a7d3053..ca93126 100644 --- a/pkg/wms130/request/getfeatureinfo_test.go +++ b/pkg/wms130/request/getfeatureinfo_test.go @@ -353,10 +353,10 @@ func compareGetFeatureInfoObject(result, expected GetFeatureInfo, t *testing.T, } if len(expected.StyledLayerDescriptor.NamedLayer) == len(result.StyledLayerDescriptor.NamedLayer) { c := false - for _, sldnl := range expected.StyledLayerDescriptor.NamedLayer { + for _, sldnamedlayer := range expected.StyledLayerDescriptor.NamedLayer { for _, result := range result.StyledLayerDescriptor.NamedLayer { - if result.Name == sldnl.Name { - if *&result.NamedStyle.Name == *&sldnl.NamedStyle.Name { + if result.Name == sldnamedlayer.Name { + if *&result.NamedStyle.Name == *&sldnamedlayer.NamedStyle.Name { c = true } } From 2e00ef75c7613afeb5f6cbaed4cf437e94326f8d Mon Sep 17 00:00:00 2001 From: Wouter Date: Wed, 5 Aug 2020 10:58:49 +0200 Subject: [PATCH 35/45] removed old code --- pkg/wms130/request/getfeatureinfo.go | 7 ------- 1 file changed, 7 deletions(-) diff --git a/pkg/wms130/request/getfeatureinfo.go b/pkg/wms130/request/getfeatureinfo.go index 61c6af6..70cef32 100644 --- a/pkg/wms130/request/getfeatureinfo.go +++ b/pkg/wms130/request/getfeatureinfo.go @@ -26,13 +26,6 @@ const ( FEATURECOUNT = `FEATURE_COUNT` ) -// var getFeatureInfoMandatoryParameters, getFeatureInfoOptionalParameters []string - -// func init() { -// getFeatureInfoMandatoryParameters = []string{LAYERS, STYLES, CRS, BBOX, WIDTH, HEIGHT, FORMAT, QUERYLAYERS, I, J} -// getFeatureInfoOptionalParameters = []string{TRANSPARENT, BGCOLOR, EXCEPTIONS, INFOFORMAT, FEATURECOUNT} -// } - // Type returns GetFeatureInfo func (gfi *GetFeatureInfo) Type() string { return getfeatureinfo From 0804722d3f33397fccbf4ee355ec7286de4edc85 Mon Sep 17 00:00:00 2001 From: Wouter Date: Wed, 5 Aug 2020 10:59:05 +0200 Subject: [PATCH 36/45] updated README import example --- README.md | 2 +- pkg/ows/operationrequestkvp.go | 30 +++++++++++++++--------------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 8b8dc93..074e3ce 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ go get github.com/pdok/ogc-specifications ``` ```import -import "github.com/pdok/ogc-specifications" +import ows "github.com/pdok/ogc-specifications/pkg/ows" ``` ## Test diff --git a/pkg/ows/operationrequestkvp.go b/pkg/ows/operationrequestkvp.go index 9b0b74a..b503ab2 100644 --- a/pkg/ows/operationrequestkvp.go +++ b/pkg/ows/operationrequestkvp.go @@ -1,15 +1,15 @@ -package ows - -import ( - "net/url" -) - -// OperationRequestKVP interface -type OperationRequestKVP interface { - Type() string - Validate(Capability) Exceptions - - ParseKVP(url.Values) Exceptions - ParseOperationsRequest(OperationRequest) Exceptions - BuildKVP() url.Values -} +package ows + +import ( + "net/url" +) + +// OperationRequestKVP interface +type OperationRequestKVP interface { + Type() string + Validate(Capability) Exceptions + + ParseKVP(url.Values) Exceptions + ParseOperationsRequest(OperationRequest) Exceptions + BuildKVP() url.Values +} From 71534ef752dbe76ff1af2f07e08e5362c39c1fd5 Mon Sep 17 00:00:00 2001 From: Wouter Date: Thu, 6 Aug 2020 12:49:14 +0200 Subject: [PATCH 37/45] CRS from string->struct --- pkg/ows/common.go | 35 ++++++++++++++++++++++++++++ pkg/ows/xml.go | 30 ++++++++++++++++++++++++ pkg/wms130/request/getfeatureinfo.go | 11 +++++++-- pkg/wms130/request/getmap.go | 30 ++++++++++++++++++++---- pkg/wms130/request/getmap_test.go | 14 +++++------ pkg/wms130/request/getmapkvp.go | 2 +- 6 files changed, 107 insertions(+), 15 deletions(-) diff --git a/pkg/ows/common.go b/pkg/ows/common.go index 2c4af80..58b6c83 100644 --- a/pkg/ows/common.go +++ b/pkg/ows/common.go @@ -3,10 +3,15 @@ package ows import ( "encoding/xml" "fmt" + "regexp" "strconv" "strings" ) +const ( + codeSpace = `urn:ogc:def:crs:EPSG::` +) + // BoundingBox struct // Base BoundingBox struct to be used for OGC Boundingbox object // What todo with Geoserver implementation... @@ -83,3 +88,33 @@ func StripDuplicateAttr(attr []xml.Attr) []xml.Attr { } return strippedAttr } + +// CRS struct with namespace/authority/registry and code +type CRS struct { + Namespace string //TODO maybe AuthorityType is a better name...? + Code string +} + +// String of the EPSGCode +func (c *CRS) String() string { + return c.Namespace + `:` + c.Code +} + +// Identifier returns the EPSG +func (c *CRS) Identifier() string { + return codeSpace + c.Code +} + +// ParseString build CRS struct from input string +func (c *CRS) ParseString(s string) { + c.parseString(s) +} + +func (c *CRS) parseString(s string) { + regex := regexp.MustCompile(`(^.*):([0-9]+)`) + code := regex.FindStringSubmatch(s) + if len(code) == 3 { // code[0] is the full match, the other the parts + c.Namespace = code[1] + c.Code = code[2] + } +} diff --git a/pkg/ows/xml.go b/pkg/ows/xml.go index b5f15c0..caa1ae6 100644 --- a/pkg/ows/xml.go +++ b/pkg/ows/xml.go @@ -64,6 +64,36 @@ func (p *Position) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { } } +// MarshalXML Position +func (c *CRS) MarshalXML(e *xml.Encoder, start xml.StartElement) error { + var s = `` + if c.Namespace != `` && c.Code != `` { + s = fmt.Sprintf("%s:%s", c.Namespace, c.Code) + } + + return e.EncodeElement(s, start) +} + +// UnmarshalXML Position +func (c *CRS) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + var crs CRS + for { + token, err := d.Token() + if err != nil { + return err + } + switch el := token.(type) { + case xml.CharData: + crs.parseString(string([]byte(el))) + case xml.EndElement: + if el == start.End() { + *c = crs + return nil + } + } + } +} + func getPositionFromString(position string) []float64 { regex := regexp.MustCompile(` `) result := regex.Split(position, -1) diff --git a/pkg/wms130/request/getfeatureinfo.go b/pkg/wms130/request/getfeatureinfo.go index 70cef32..6b739d4 100644 --- a/pkg/wms130/request/getfeatureinfo.go +++ b/pkg/wms130/request/getfeatureinfo.go @@ -32,8 +32,15 @@ func (gfi *GetFeatureInfo) Type() string { } // Validate returns GetFeatureInfo -func (gfi *GetFeatureInfo) Validate(c capabilities.Capability) ows.Exceptions { - return nil +func (gfi *GetFeatureInfo) Validate(c ows.Capability) ows.Exceptions { + var exceptions ows.Exceptions + + wmsCapabilities := c.(capabilities.Capability) + + exceptions = append(exceptions, gfi.StyledLayerDescriptor.Validate(wmsCapabilities)...) + // exceptions = append(exceptions, gfi.Output.Validate(wmsCapabilities)...) + + return exceptions } // ParseXML builds a GetFeatureInfo object based on a XML document diff --git a/pkg/wms130/request/getmap.go b/pkg/wms130/request/getmap.go index bc5bb99..918d519 100644 --- a/pkg/wms130/request/getmap.go +++ b/pkg/wms130/request/getmap.go @@ -43,14 +43,25 @@ func (gm *GetMap) Type() string { func (gm *GetMap) Validate(c ows.Capability) ows.Exceptions { var exceptions ows.Exceptions - getmapcap := c.(capabilities.Capability) + wmsCapabilities := c.(capabilities.Capability) - exceptions = append(exceptions, gm.StyledLayerDescriptor.Validate(getmapcap)...) - exceptions = append(exceptions, gm.Output.Validate(getmapcap)...) + exceptions = append(exceptions, gm.StyledLayerDescriptor.Validate(wmsCapabilities)...) + exceptions = append(exceptions, gm.Output.Validate(wmsCapabilities)...) return exceptions } +// CheckCRS against a given list of CRS +func CheckCRS(crs string, definedCrs []string) ows.Exception { + for _, defined := range definedCrs { + if defined == crs { + return nil + } + } + + return exception.InvalidCRS(crs) +} + // ParseGetMapKVP process the simple struct to a complex struct func (gm *GetMap) ParseGetMapKVP(gmkvp GetMapKVP) ows.Exception { gm.BaseRequest.Build(gmkvp.Service, gmkvp.Version) @@ -61,7 +72,16 @@ func (gm *GetMap) ParseGetMapKVP(gmkvp GetMapKVP) ows.Exception { } gm.StyledLayerDescriptor = sld - gm.CRS = gmkvp.CRS + // if !strings.Contains(gmkvp.CRS, `EPSG`) { + // return exception.InvalidCRS(gmkvp.CRS) + // } + + // regex := regexp.MustCompile(`[0-9]+`) + // code := regex.FindString((gmkvp.CRS)) + + var crs ows.CRS + crs.ParseString(gmkvp.CRS) + gm.CRS = crs var bbox ows.BoundingBox if err := bbox.Build(gmkvp.Bbox); err != nil { @@ -216,7 +236,7 @@ type GetMap struct { XMLName xml.Name `xml:"GetMap" yaml:"getmap"` BaseRequest StyledLayerDescriptor StyledLayerDescriptor `xml:"StyledLayerDescriptor" yaml:"styledlayerdescriptor"` - CRS string `xml:"CRS" yaml:"crs"` + CRS ows.CRS `xml:"CRS" yaml:"crs"` BoundingBox ows.BoundingBox `xml:"BoundingBox" yaml:"boundingbox"` Output Output `xml:"Output" yaml:"output"` Exceptions *string `xml:"Exceptions" yaml:"exceptions"` diff --git a/pkg/wms130/request/getmap_test.go b/pkg/wms130/request/getmap_test.go index 9acd4e9..7f1418d 100644 --- a/pkg/wms130/request/getmap_test.go +++ b/pkg/wms130/request/getmap_test.go @@ -179,7 +179,7 @@ func TestGetMapParseXML(t *testing.T) { {Name: "Roads", NamedStyle: &NamedStyle{Name: "CenterLine"}}, {Name: "Houses", NamedStyle: &NamedStyle{Name: "Outline"}}, }}, - CRS: "EPSG:4326", + CRS: ows.CRS{Namespace: "EPSG", Code: "4326"}, BoundingBox: ows.BoundingBox{ Crs: "http://www.opengis.net/gml/srs/epsg.xml#4326", LowerCorner: [2]float64{-180.0, -90.0}, @@ -275,7 +275,7 @@ func TestGetMapParseKVP(t *testing.T) { Excepted GetMap Exception ows.Exception }{ - 0: {Query: map[string][]string{REQUEST: {getmap}, SERVICE: {Service}, VERSION: {Version}}, + 0: {Query: map[string][]string{REQUEST: {getmap}, CRS: {`CRS:84`}, SERVICE: {Service}, VERSION: {Version}}, Exception: ows.InvalidParameterValue(``, `boundingbox`), }, 1: {Query: url.Values{}, @@ -303,7 +303,7 @@ func TestGetMapParseKVP(t *testing.T) { {Name: "Roads", NamedStyle: &NamedStyle{Name: "CenterLine"}}, {Name: "Houses", NamedStyle: &NamedStyle{Name: "Outline"}}, }}, - CRS: "EPSG:4326", + CRS: ows.CRS{Namespace: "EPSG", Code: "4326"}, BoundingBox: ows.BoundingBox{ LowerCorner: [2]float64{-180.0, -90.0}, UpperCorner: [2]float64{180.0, 90.0}, @@ -347,7 +347,7 @@ func TestGetMapBuildKVP(t *testing.T) { {Name: "Roads", NamedStyle: &NamedStyle{Name: "CenterLine"}}, {Name: "Houses", NamedStyle: &NamedStyle{Name: "Outline"}}, }}, - CRS: "EPSG:4326", + CRS: ows.CRS{Namespace: "EPSG", Code: "4326"}, BoundingBox: ows.BoundingBox{ LowerCorner: [2]float64{-180.0, -90.0}, UpperCorner: [2]float64{180.0, 90.0}, @@ -372,7 +372,7 @@ func TestGetMapBuildKVP(t *testing.T) { SERVICE: {`WMS`}, }}, 1: {Object: GetMap{ - CRS: "EPSG:4326", + CRS: ows.CRS{Namespace: "EPSG", Code: "4326"}, BoundingBox: ows.BoundingBox{ LowerCorner: [2]float64{-180.0, -90.0}, UpperCorner: [2]float64{180.0, 90.0}, @@ -534,7 +534,7 @@ func BenchmarkGetMapBuildKVP(b *testing.B) { {Name: "Roads", NamedStyle: &NamedStyle{Name: "CenterLine"}}, {Name: "Houses", NamedStyle: &NamedStyle{Name: "Outline"}}, }}, - CRS: "EPSG:4326", + CRS: ows.CRS{Namespace: "EPSG", Code: "4326"}, BoundingBox: ows.BoundingBox{ Crs: "http://www.opengis.net/gml/srs/epsg.xml#4326", LowerCorner: [2]float64{-180.0, -90.0}, @@ -572,7 +572,7 @@ func BenchmarkGetMapBuildXML(b *testing.B) { {Name: "Roads", NamedStyle: &NamedStyle{Name: "CenterLine"}}, {Name: "Houses", NamedStyle: &NamedStyle{Name: "Outline"}}, }}, - CRS: "EPSG:4326", + CRS: ows.CRS{Namespace: "EPSG", Code: "4326"}, BoundingBox: ows.BoundingBox{ Crs: "http://www.opengis.net/gml/srs/epsg.xml#4326", LowerCorner: [2]float64{-180.0, -90.0}, diff --git a/pkg/wms130/request/getmapkvp.go b/pkg/wms130/request/getmapkvp.go index 77bb10d..ae38ea9 100644 --- a/pkg/wms130/request/getmapkvp.go +++ b/pkg/wms130/request/getmapkvp.go @@ -98,7 +98,7 @@ func (gmkvp *GetMapKVP) ParseOperationsRequest(gm *GetMap) ows.Exceptions { gmkvp.Service = Service gmkvp.Layers = gm.StyledLayerDescriptor.getLayerKVPValue() gmkvp.Styles = gm.StyledLayerDescriptor.getStyleKVPValue() - gmkvp.CRS = gm.CRS + gmkvp.CRS = gm.CRS.String() gmkvp.Bbox = gm.BoundingBox.BuildKVP() gmkvp.Width = strconv.Itoa(gm.Output.Size.Width) gmkvp.Height = strconv.Itoa(gm.Output.Size.Height) From ea03a2ca7abbf0143d9ea7a760a57c52747f2401 Mon Sep 17 00:00:00 2001 From: Wouter Date: Thu, 6 Aug 2020 14:46:32 +0200 Subject: [PATCH 38/45] finalizing init setup wms pkg + interfacing + func naming --- pkg/ows/common.go | 4 +- pkg/ows/common_test.go | 2 +- pkg/ows/operationrequest.go | 1 + pkg/ows/operationrequestkvp.go | 8 +-- pkg/wms130/request/common.go | 2 +- pkg/wms130/request/getcapabilities.go | 55 ++++++++++-------- pkg/wms130/request/getcapabilities_test.go | 57 +++++++++++-------- pkg/wms130/request/getcapabilitieskvp.go | 66 ++++++++++++++++++++++ pkg/wms130/request/getfeatureinfo.go | 27 +++++---- pkg/wms130/request/getfeatureinfo_test.go | 8 +-- pkg/wms130/request/getfeatureinfokvp.go | 10 ++-- pkg/wms130/request/getmap.go | 44 +++++++-------- pkg/wms130/request/getmap_test.go | 4 +- pkg/wms130/request/getmapkvp.go | 10 ++-- 14 files changed, 195 insertions(+), 103 deletions(-) create mode 100644 pkg/wms130/request/getcapabilitieskvp.go diff --git a/pkg/ows/common.go b/pkg/ows/common.go index 58b6c83..ebc3afe 100644 --- a/pkg/ows/common.go +++ b/pkg/ows/common.go @@ -35,8 +35,8 @@ func (b *BoundingBox) BuildKVP() string { return fmt.Sprintf("%f,%f,%f,%f", b.LowerCorner[0], b.LowerCorner[1], b.UpperCorner[0], b.UpperCorner[1]) } -//Build builds a BoundingBox -func (b *BoundingBox) Build(boundingbox string) Exception { +//ParseString builds a BoundingBox based on a string +func (b *BoundingBox) ParseString(boundingbox string) Exception { result := strings.Split(boundingbox, ",") var lx, ly, ux, uy float64 var err error diff --git a/pkg/ows/common_test.go b/pkg/ows/common_test.go index 3dcc16c..d0b4c2e 100644 --- a/pkg/ows/common_test.go +++ b/pkg/ows/common_test.go @@ -38,7 +38,7 @@ func TestBuildBoundingBox(t *testing.T) { for k, test := range tests { var bbox BoundingBox - if err := bbox.Build(test.boundingbox); err != nil { + if err := bbox.ParseString(test.boundingbox); err != nil { if err != test.Exception { t.Errorf("test: %d, expected: %+v \ngot: %+v", k, test.Exception, err) } diff --git a/pkg/ows/operationrequest.go b/pkg/ows/operationrequest.go index d68768d..7a364c1 100644 --- a/pkg/ows/operationrequest.go +++ b/pkg/ows/operationrequest.go @@ -11,6 +11,7 @@ type OperationRequest interface { ParseKVP(url.Values) Exceptions BuildXML() []byte BuildKVP() url.Values + ParseOperationRequestKVP(OperationRequestKVP) Exceptions // TODO YAML support // ParseYAML([]byte) Exceptions diff --git a/pkg/ows/operationrequestkvp.go b/pkg/ows/operationrequestkvp.go index b503ab2..f896db1 100644 --- a/pkg/ows/operationrequestkvp.go +++ b/pkg/ows/operationrequestkvp.go @@ -5,11 +5,11 @@ import ( ) // OperationRequestKVP interface +// This interface is a layer in front of a OperationRequest struct +// to translate KVP to OperationRequest structs and generating +// OperationRequestKVP struct from OperationRequests type OperationRequestKVP interface { - Type() string - Validate(Capability) Exceptions - ParseKVP(url.Values) Exceptions - ParseOperationsRequest(OperationRequest) Exceptions + ParseOperationRequest(OperationRequest) Exceptions BuildKVP() url.Values } diff --git a/pkg/wms130/request/common.go b/pkg/wms130/request/common.go index eea7d40..f6c1c4b 100644 --- a/pkg/wms130/request/common.go +++ b/pkg/wms130/request/common.go @@ -12,7 +12,7 @@ const ( Version string = `1.3.0` ) -// WMS 1.3.0 Tokens +// WMS 1.3.0 Keys const ( SERVICE = `SERVICE` REQUEST = `REQUEST` diff --git a/pkg/wms130/request/getcapabilities.go b/pkg/wms130/request/getcapabilities.go index c003286..84346c6 100644 --- a/pkg/wms130/request/getcapabilities.go +++ b/pkg/wms130/request/getcapabilities.go @@ -7,7 +7,6 @@ import ( "strings" "github.com/pdok/ogc-specifications/pkg/ows" - "github.com/pdok/ogc-specifications/pkg/wms130/capabilities" ) // @@ -21,8 +20,9 @@ func (gc *GetCapabilities) Type() string { } // Validate returns GetCapabilities -func (gc *GetCapabilities) Validate(c capabilities.Capability) ows.Exceptions { - return nil +func (gc *GetCapabilities) Validate(c ows.Capability) ows.Exceptions { + var exceptions ows.Exceptions + return exceptions } // ParseXML builds a GetCapabilities object based on a XML document @@ -50,29 +50,40 @@ func (gc *GetCapabilities) ParseXML(body []byte) ows.Exceptions { // ParseKVP builds a GetCapabilities object based on the available query parameters func (gc *GetCapabilities) ParseKVP(query url.Values) ows.Exceptions { - for k, v := range query { - switch strings.ToUpper(k) { - case REQUEST: - if strings.ToUpper(v[0]) == strings.ToUpper(getcapabilities) { - gc.XMLName.Local = getcapabilities - } - case SERVICE: - gc.Service = strings.ToUpper(v[0]) - case VERSION: - gc.Version = strings.ToUpper(v[0]) - } + if len(query) == 0 { + // When there are no query value we know that at least + // the manadorty SERVICE and REQUEST parameter is missing. + return ows.Exceptions{ows.MissingParameterValue(SERVICE), ows.MissingParameterValue(REQUEST)} } + + gckvp := GetCapabilitiesKVP{} + if err := gckvp.ParseKVP(query); err != nil { + return err + } + + if err := gc.ParseOperationRequestKVP(&gckvp); err != nil { + return err + } + + return nil +} + +// ParseOperationRequestKVP process the simple struct to a complex struct +func (gc *GetCapabilities) ParseOperationRequestKVP(orkvp ows.OperationRequestKVP) ows.Exceptions { + gckvp := orkvp.(*GetCapabilitiesKVP) + + gc.XMLName.Local = gckvp.Request + gc.BaseRequest.Build(gckvp.Service, gckvp.Version) return nil } // BuildKVP builds a new query string that will be proxied func (gc *GetCapabilities) BuildKVP() url.Values { - querystring := make(map[string][]string) - querystring[REQUEST] = []string{gc.XMLName.Local} - querystring[SERVICE] = []string{gc.Service} - querystring[VERSION] = []string{gc.Version} + gckvp := GetCapabilitiesKVP{} + gckvp.ParseOperationRequest(gc) - return querystring + kvp := gckvp.BuildKVP() + return kvp } // BuildXML builds a 'new' XML document 'based' on the 'original' XML document @@ -84,8 +95,6 @@ func (gc *GetCapabilities) BuildXML() []byte { // GetCapabilities struct with the needed parameters/attributes needed for making a GetCapabilities request type GetCapabilities struct { - XMLName xml.Name `xml:"GetCapabilities" yaml:"getcapabilities"` - Service string `xml:"service,attr" yaml:"service"` - Version string `xml:"version,attr" yaml:"version"` - Attr ows.XMLAttribute `xml:",attr"` + XMLName xml.Name `xml:"GetCapabilities" yaml:"getcapabilities"` + BaseRequest } diff --git a/pkg/wms130/request/getcapabilities_test.go b/pkg/wms130/request/getcapabilities_test.go index e078881..43f54e7 100644 --- a/pkg/wms130/request/getcapabilities_test.go +++ b/pkg/wms130/request/getcapabilities_test.go @@ -23,8 +23,7 @@ func TestGetCapabilitiesParseXML(t *testing.T) { }{ // GetCapabilities 0: {Body: []byte(``), - Result: GetCapabilities{XMLName: xml.Name{Local: "GetCapabilities"}, Service: "wms", Version: "1.3.0", - Attr: []xml.Attr{{Name: xml.Name{Local: "xmlns"}, Value: "http://www.opengis.net/wms"}}}}, + Result: GetCapabilities{XMLName: xml.Name{Local: "GetCapabilities"}, BaseRequest: BaseRequest{Service: "wms", Version: "1.3.0", Attr: []xml.Attr{{Name: xml.Name{Local: "xmlns"}, Value: "http://www.opengis.net/wms"}}}}}, // Unknown XML document 1: {Body: []byte(""), Error: ows.MissingParameterValue("REQUEST")}, // no XML document @@ -69,45 +68,53 @@ func TestGetCapabilitiesParseXML(t *testing.T) { func TestGetCapabilitiesParseKVP(t *testing.T) { var tests = []struct { - Query url.Values - Result GetCapabilities - Error error + Query url.Values + Result GetCapabilities + Exceptions ows.Exceptions }{ // "Normal" query request with UPPER/lower/MiXeD case 0: {Query: map[string][]string{"SERVICE": {"wms"}, "Request": {"GetCapabilities"}, "version": {"1.3.0"}}, - Result: GetCapabilities{XMLName: xml.Name{Local: "GetCapabilities"}, Service: Service, Version: Version}}, + Result: GetCapabilities{XMLName: xml.Name{Local: "GetCapabilities"}, BaseRequest: BaseRequest{Service: Service, Version: Version}}}, // Missing mandatory SERVICE attribute 1: {Query: map[string][]string{"Request": {"GetCapabilities"}}, Result: GetCapabilities{XMLName: xml.Name{Local: "GetCapabilities"}}}, // Missing optional VERSION attribute 2: {Query: map[string][]string{"SERVICE": {"wms"}, "Request": {"GetCapabilities"}}, - Result: GetCapabilities{XMLName: xml.Name{Local: "GetCapabilities"}, Service: Service}}, + Result: GetCapabilities{XMLName: xml.Name{Local: "GetCapabilities"}, BaseRequest: BaseRequest{Service: Service}}}, // Unknown optional VERSION attribute 3: {Query: map[string][]string{"SERVICE": {"wms"}, "Request": {"GetCapabilities"}, "version": {"3.4.5"}}, - Result: GetCapabilities{XMLName: xml.Name{Local: "GetCapabilities"}, Service: Service, Version: "3.4.5"}}, + Result: GetCapabilities{XMLName: xml.Name{Local: "GetCapabilities"}, BaseRequest: BaseRequest{Service: Service, Version: "3.4.5"}}}, 4: {Query: map[string][]string{"SERVICE": {"wms"}, "Request": {"GetCapabilities"}, "version": {"no version found"}}, - Result: GetCapabilities{XMLName: xml.Name{Local: "GetCapabilities"}, Service: Service, Version: "NO VERSION FOUND"}}, + Result: GetCapabilities{XMLName: xml.Name{Local: "GetCapabilities"}, BaseRequest: BaseRequest{Service: Service, Version: "no version found"}}}, // No mandatory SERVICE, REQUEST attribute only optional VERSION 5: { - Error: ows.MissingParameterValue()}, + Exceptions: ows.Exceptions{ows.MissingParameterValue(REQUEST), ows.MissingParameterValue(SERVICE)}}, } - for k, n := range tests { + for k, test := range tests { var gc GetCapabilities - err := gc.ParseKVP(n.Query) - if err != nil { - if err[0].Error() != n.Error.Error() { - t.Errorf("test: %d, expected: %s,\n got: %s", k, n.Error, err) + errs := gc.ParseKVP(test.Query) + if len(errs) > 0 { + for _, err := range errs { + found := false + for _, exception := range test.Exceptions { + if err == exception { + found = true + } + } + if !found { + t.Errorf("test exception: %d, expected one of: %s ,\n got: %s", k, test.Exceptions, err.Error()) + } } } else { - if n.Result.XMLName.Local != gc.XMLName.Local { - t.Errorf("test: %d, expected: %s ,\n got: %s", k, n.Result.XMLName.Local, gc.XMLName.Local) + if test.Result.XMLName.Local != gc.XMLName.Local { + t.Errorf("test: %d, expected: %s ,\n got: %s", k, test.Result.XMLName.Local, gc.XMLName.Local) } - if n.Result.Service != gc.Service { - t.Errorf("test: %d, expected: %s ,\n got: %s", k, n.Result.Service, gc.Service) + if test.Result.Service != gc.Service { + t.Errorf("test: %d, expected: %s ,\n got: %s", k, test.Result.Service, gc.Service) } - if n.Result.Version != gc.Version { - t.Errorf("test: %d, expected: %s ,\n got: %s", k, n.Result.Version, gc.Version) + if test.Result.Version != gc.Version { + t.Errorf("test: %d, expected: %s ,\n got: %s", k, test.Result.Version, gc.Version) } } } @@ -119,7 +126,7 @@ func TestGetCapabilitiesBuildKVP(t *testing.T) { Excepted url.Values Error ows.Exception }{ - 0: {Object: GetCapabilities{Service: Service, Version: Version, XMLName: xml.Name{Local: `GetCapabilities`}}, + 0: {Object: GetCapabilities{BaseRequest: BaseRequest{Service: Service, Version: Version}, XMLName: xml.Name{Local: `GetCapabilities`}}, Excepted: map[string][]string{ VERSION: {Version}, SERVICE: {Service}, @@ -153,7 +160,7 @@ func TestGetCapabilitiesBuildXML(t *testing.T) { gc GetCapabilities result string }{ - 0: {gc: GetCapabilities{Service: Service, Version: Version, XMLName: xml.Name{Local: `GetCapabilities`}}, + 0: {gc: GetCapabilities{BaseRequest: BaseRequest{Service: Service, Version: Version}, XMLName: xml.Name{Local: `GetCapabilities`}}, result: ` `}, } @@ -172,14 +179,14 @@ func TestGetCapabilitiesBuildXML(t *testing.T) { // ---------- func BenchmarkGetCapabilitiesBuildKVP(b *testing.B) { - gc := GetCapabilities{XMLName: xml.Name{Local: getcapabilities}, Service: Service, Version: Version} + gc := GetCapabilities{XMLName: xml.Name{Local: getcapabilities}, BaseRequest: BaseRequest{Service: Service, Version: Version}} for i := 0; i < b.N; i++ { gc.BuildKVP() } } func BenchmarkGetCapabilitiesBuildXML(b *testing.B) { - gc := GetCapabilities{XMLName: xml.Name{Local: getcapabilities}, Service: Service, Version: Version} + gc := GetCapabilities{XMLName: xml.Name{Local: getcapabilities}, BaseRequest: BaseRequest{Service: Service, Version: Version}} for i := 0; i < b.N; i++ { gc.BuildXML() } diff --git a/pkg/wms130/request/getcapabilitieskvp.go b/pkg/wms130/request/getcapabilitieskvp.go new file mode 100644 index 0000000..d5174cf --- /dev/null +++ b/pkg/wms130/request/getcapabilitieskvp.go @@ -0,0 +1,66 @@ +package request + +import ( + "net/url" + "strings" + + "github.com/pdok/ogc-specifications/pkg/ows" +) + +//GetCapabilitiesKVP struct +type GetCapabilitiesKVP struct { + // Table 8 - The Parameters of a GetMap request + Service string `yaml:"service,omitempty"` + BaseRequestKVP +} + +// ParseKVP builds a GetCapabilities object based on the available query parameters +func (gckvp *GetCapabilitiesKVP) ParseKVP(query url.Values) ows.Exceptions { + var exceptions ows.Exceptions + for k, v := range query { + if len(v) != 1 { + exceptions = append(exceptions, ows.InvalidParameterValue(k, strings.Join(v, ","))) + } else { + switch strings.ToUpper(k) { + case SERVICE: + gckvp.Service = strings.ToUpper(v[0]) + case VERSION: + gckvp.BaseRequestKVP.Version = v[0] + case REQUEST: + gckvp.BaseRequestKVP.Request = v[0] + } + } + } + + if len(exceptions) > 0 { + return exceptions + } + + return nil +} + +// ParseOperationRequest builds a GetCapabilitiesKVP object based on a GetCapabilities struct +// This is a 'dummy' implementation, because for a GetCapabilities request it will always be +// Mandatory: REQUEST=GetCapabilities +// SERVICE=WMS +// Optional: VERSION=1.3.0 +func (gckvp *GetCapabilitiesKVP) ParseOperationRequest(or ows.OperationRequest) ows.Exceptions { + gc := or.(*GetCapabilities) + + gckvp.Request = getcapabilities + gckvp.Version = gc.Version + gckvp.Service = gc.Service + + return nil +} + +// BuildKVP builds a url.Values query from a GetMapKVP struct +func (gckvp *GetCapabilitiesKVP) BuildKVP() url.Values { + query := make(map[string][]string) + + query[SERVICE] = []string{gckvp.Service} + query[VERSION] = []string{gckvp.Version} + query[REQUEST] = []string{gckvp.Request} + + return query +} diff --git a/pkg/wms130/request/getfeatureinfo.go b/pkg/wms130/request/getfeatureinfo.go index 6b739d4..a68eee8 100644 --- a/pkg/wms130/request/getfeatureinfo.go +++ b/pkg/wms130/request/getfeatureinfo.go @@ -12,16 +12,20 @@ import ( "github.com/pdok/ogc-specifications/pkg/wms130/exception" ) -// +// GetFeatureInfo const ( getfeatureinfo = `GetFeatureInfo` +) - // Mandatory +// Mandatory GetFeatureInfo Keys +const ( QUERYLAYERS = `QUERY_LAYERS` I = `I` J = `J` +) - // Optional +// Optional GetFeatureInfo Keys +const ( INFOFORMAT = `INFO_FORMAT` FEATURECOUNT = `FEATURE_COUNT` ) @@ -69,10 +73,11 @@ func (gfi *GetFeatureInfo) ParseXML(body []byte) ows.Exceptions { return nil } -// ParseGetFeatureInfoKVP process the simple struct to a complex struct -func (gfi *GetFeatureInfo) ParseGetFeatureInfoKVP(gfikvp GetFeatureInfoKVP) ows.Exceptions { +// ParseOperationRequestKVP process the simple struct to a complex struct +func (gfi *GetFeatureInfo) ParseOperationRequestKVP(orkvp ows.OperationRequestKVP) ows.Exceptions { + gfikvp := orkvp.(*GetFeatureInfoKVP) - // Base + gfi.XMLName.Local = getfeatureinfo gfi.BaseRequest.Build(gfikvp.Service, gfikvp.Version) sld, ex := gfikvp.BuildStyledLayerDescriptor() @@ -84,7 +89,7 @@ func (gfi *GetFeatureInfo) ParseGetFeatureInfoKVP(gfikvp GetFeatureInfoKVP) ows. gfi.CRS = gfikvp.CRS var bbox ows.BoundingBox - if err := bbox.Build(gfikvp.Bbox); err != nil { + if err := bbox.ParseString(gfikvp.Bbox); err != nil { return ows.Exceptions{err} } gfi.BoundingBox = bbox @@ -133,8 +138,8 @@ func (gfi *GetFeatureInfo) ParseGetFeatureInfoKVP(gfikvp GetFeatureInfoKVP) ows. func (gfi *GetFeatureInfo) ParseKVP(query url.Values) ows.Exceptions { if len(query) == 0 { // When there are no query value we know that at least - // the manadorty VERSION parameter is missing. - return ows.Exceptions{ows.MissingParameterValue(VERSION)} + // the manadorty VERSION and REQUEST parameter is missing. + return ows.Exceptions{ows.MissingParameterValue(VERSION), ows.MissingParameterValue(REQUEST)} } gfikvp := GetFeatureInfoKVP{} @@ -142,7 +147,7 @@ func (gfi *GetFeatureInfo) ParseKVP(query url.Values) ows.Exceptions { return err } - if err := gfi.ParseGetFeatureInfoKVP(gfikvp); err != nil { + if err := gfi.ParseOperationRequestKVP(&gfikvp); err != nil { return err } @@ -152,7 +157,7 @@ func (gfi *GetFeatureInfo) ParseKVP(query url.Values) ows.Exceptions { // BuildKVP builds a new query string that will be proxied func (gfi *GetFeatureInfo) BuildKVP() url.Values { gfikvp := GetFeatureInfoKVP{} - gfikvp.ParseOperationsRequest(gfi) + gfikvp.ParseOperationRequest(gfi) kvp := gfikvp.BuildKVP() return kvp diff --git a/pkg/wms130/request/getfeatureinfo_test.go b/pkg/wms130/request/getfeatureinfo_test.go index ca93126..b7671a6 100644 --- a/pkg/wms130/request/getfeatureinfo_test.go +++ b/pkg/wms130/request/getfeatureinfo_test.go @@ -19,9 +19,9 @@ func TestGetFeatureInfoType(t *testing.T) { func TestGetFeatureInfoBuildKVP(t *testing.T) { var tests = []struct { - Object GetFeatureInfo - Excepted url.Values - Error ows.Exception + Object GetFeatureInfo + Excepted url.Values + Exception ows.Exception }{ 0: {Object: GetFeatureInfo{ XMLName: xml.Name{Local: `GetFeatureInfo`}, @@ -172,7 +172,7 @@ func TestGetFeatureInfoParseKVP(t *testing.T) { Exceptions ows.Exceptions }{ 0: {Query: map[string][]string{REQUEST: {getfeatureinfo}, SERVICE: {Service}, VERSION: {Version}}, Exceptions: ows.Exceptions{ows.InvalidParameterValue("", `boundingbox`)}}, - 1: {Query: url.Values{}, Exceptions: ows.Exceptions{ows.MissingParameterValue(VERSION)}}, + 1: {Query: url.Values{}, Exceptions: ows.Exceptions{ows.MissingParameterValue(VERSION), ows.MissingParameterValue(REQUEST)}}, 2: {Query: map[string][]string{REQUEST: {getmap}, SERVICE: {Service}, VERSION: {Version}, LAYERS: {`Rivers,Roads,Houses`}, STYLES: {`CenterLine,,Outline`}, diff --git a/pkg/wms130/request/getfeatureinfokvp.go b/pkg/wms130/request/getfeatureinfokvp.go index 1b7d34a..e82cedf 100644 --- a/pkg/wms130/request/getfeatureinfokvp.go +++ b/pkg/wms130/request/getfeatureinfokvp.go @@ -39,9 +39,9 @@ func (gfikvp *GetFeatureInfoKVP) ParseKVP(query url.Values) ows.Exceptions { if len(v) != 1 { exceptions = append(exceptions, ows.InvalidParameterValue(k, strings.Join(v, ","))) } else { - switch k { + switch strings.ToUpper(k) { case SERVICE: - gfikvp.Service = v[0] + gfikvp.Service = strings.ToUpper(v[0]) case VERSION: gfikvp.BaseRequestKVP.Version = v[0] case REQUEST: @@ -118,8 +118,10 @@ func (gfikvp *GetFeatureInfoKVP) BuildKVP() url.Values { return query } -// ParseOperationsRequest builds a GetFeatureInfoKVP object based on a GetFeatureInfo struct -func (gfikvp *GetFeatureInfoKVP) ParseOperationsRequest(gfi *GetFeatureInfo) ows.Exceptions { +// ParseOperationRequest builds a GetFeatureInfoKVP object based on a GetFeatureInfo struct +func (gfikvp *GetFeatureInfoKVP) ParseOperationRequest(or ows.OperationRequest) ows.Exceptions { + gfi := or.(*GetFeatureInfo) + gfikvp.Request = getfeatureinfo gfikvp.Version = Version gfikvp.Service = Service diff --git a/pkg/wms130/request/getmap.go b/pkg/wms130/request/getmap.go index 918d519..bdfb2c8 100644 --- a/pkg/wms130/request/getmap.go +++ b/pkg/wms130/request/getmap.go @@ -11,11 +11,13 @@ import ( "github.com/pdok/ogc-specifications/pkg/wms130/exception" ) -// +// GetMap const ( getmap = `GetMap` +) - // Mandatory +// Mandatory GetMap Keys +const ( LAYERS = `LAYERS` STYLES = `STYLES` CRS = `CRS` @@ -23,8 +25,10 @@ const ( WIDTH = `WIDTH` HEIGHT = `HEIGHT` FORMAT = `FORMAT` +) - //Optional +// Optional GetMap Keys +const ( TRANSPARENT = `TRANSPARENT` BGCOLOR = `BGCOLOR` EXCEPTIONS = `EXCEPTIONS` // defaults to XML @@ -62,36 +66,32 @@ func CheckCRS(crs string, definedCrs []string) ows.Exception { return exception.InvalidCRS(crs) } -// ParseGetMapKVP process the simple struct to a complex struct -func (gm *GetMap) ParseGetMapKVP(gmkvp GetMapKVP) ows.Exception { +// ParseOperationRequestKVP process the simple struct to a complex struct +func (gm *GetMap) ParseOperationRequestKVP(orkvp ows.OperationRequestKVP) ows.Exceptions { + gmkvp := orkvp.(*GetMapKVP) + + gm.XMLName.Local = getmap gm.BaseRequest.Build(gmkvp.Service, gmkvp.Version) sld, err := gmkvp.BuildStyledLayerDescriptor() if err != nil { - return err + return ows.Exceptions{err} } gm.StyledLayerDescriptor = sld - // if !strings.Contains(gmkvp.CRS, `EPSG`) { - // return exception.InvalidCRS(gmkvp.CRS) - // } - - // regex := regexp.MustCompile(`[0-9]+`) - // code := regex.FindString((gmkvp.CRS)) - var crs ows.CRS crs.ParseString(gmkvp.CRS) gm.CRS = crs var bbox ows.BoundingBox - if err := bbox.Build(gmkvp.Bbox); err != nil { - return err + if err := bbox.ParseString(gmkvp.Bbox); err != nil { + return ows.Exceptions{err} } gm.BoundingBox = bbox output, err := gmkvp.BuildOutput() if err != nil { - return err + return ows.Exceptions{err} } gm.Output = output @@ -104,8 +104,8 @@ func (gm *GetMap) ParseGetMapKVP(gmkvp GetMapKVP) ows.Exception { func (gm *GetMap) ParseKVP(query url.Values) ows.Exceptions { if len(query) == 0 { // When there are no query values we know that at least - // the manadorty VERSION parameter is missing. - return ows.Exceptions{ows.MissingParameterValue(VERSION)} + // the manadorty VERSION and REQUEST parameter is missing. + return ows.Exceptions{ows.MissingParameterValue(VERSION), ows.MissingParameterValue(REQUEST)} } gmkvp := GetMapKVP{} @@ -113,8 +113,8 @@ func (gm *GetMap) ParseKVP(query url.Values) ows.Exceptions { return err } - if err := gm.ParseGetMapKVP(gmkvp); err != nil { - return ows.Exceptions{err} + if err := gm.ParseOperationRequestKVP(&gmkvp); err != nil { + return err } return nil @@ -142,7 +142,7 @@ func (gm *GetMap) ParseXML(body []byte) ows.Exceptions { // BuildKVP builds a new query string that will be proxied func (gm *GetMap) BuildKVP() url.Values { gmkvp := GetMapKVP{} - gmkvp.ParseOperationsRequest(gm) + gmkvp.ParseOperationRequest(gm) kvp := gmkvp.BuildKVP() return kvp @@ -242,7 +242,7 @@ type GetMap struct { Exceptions *string `xml:"Exceptions" yaml:"exceptions"` // TODO: something with Time & Elevation // Elevation *[]Elevation `xml:"Elevation" yaml:"elevation"` - // Time *string `xml:"Time" yaml:"time"` + // Time *string `xml:"Time" yaml:"time"`BuildKVP } // Validate validates the output parameters diff --git a/pkg/wms130/request/getmap_test.go b/pkg/wms130/request/getmap_test.go index 7f1418d..e056bc0 100644 --- a/pkg/wms130/request/getmap_test.go +++ b/pkg/wms130/request/getmap_test.go @@ -70,7 +70,7 @@ func TestValidateStyledLayerDescriptor(t *testing.T) { var tests = []struct { capabilities capabilities.Capability sld StyledLayerDescriptor - exceptions []ows.Exception + exceptions ows.Exceptions }{ 0: { capabilities: capabilities.Capability{ @@ -93,7 +93,7 @@ func TestValidateStyledLayerDescriptor(t *testing.T) { }, }, sld: StyledLayerDescriptor{NamedLayer: []NamedLayer{{Name: "layer1"}, {Name: "layer2", NamedStyle: &NamedStyle{Name: `styletwo`}}}}, - exceptions: []ows.Exception{exception.LayerNotDefined(`layer1`), exception.StyleNotDefined(`styletwo`, `layer2`)}, + exceptions: ows.Exceptions{exception.LayerNotDefined(`layer1`), exception.StyleNotDefined(`styletwo`, `layer2`)}, }, } diff --git a/pkg/wms130/request/getmapkvp.go b/pkg/wms130/request/getmapkvp.go index ae38ea9..12727d2 100644 --- a/pkg/wms130/request/getmapkvp.go +++ b/pkg/wms130/request/getmapkvp.go @@ -50,9 +50,9 @@ func (gmkvp *GetMapKVP) ParseKVP(query url.Values) ows.Exceptions { if len(v) != 1 { exceptions = append(exceptions, ows.InvalidParameterValue(k, strings.Join(v, ","))) } else { - switch k { + switch strings.ToUpper(k) { case SERVICE: - gmkvp.Service = v[0] + gmkvp.Service = strings.ToUpper(v[0]) case VERSION: gmkvp.BaseRequestKVP.Version = v[0] case REQUEST: @@ -91,8 +91,10 @@ func (gmkvp *GetMapKVP) ParseKVP(query url.Values) ows.Exceptions { return nil } -// ParseOperationsRequest builds a GetMapKVP object based on a GetMap struct -func (gmkvp *GetMapKVP) ParseOperationsRequest(gm *GetMap) ows.Exceptions { +// ParseOperationRequest builds a GetMapKVP object based on a GetMap struct +func (gmkvp *GetMapKVP) ParseOperationRequest(or ows.OperationRequest) ows.Exceptions { + gm := or.(*GetMap) + gmkvp.Request = getmap gmkvp.Version = Version gmkvp.Service = Service From 85bcc9644e494ef76d87ec3eeaf56847827d739a Mon Sep 17 00:00:00 2001 From: Wouter Date: Thu, 6 Aug 2020 14:51:19 +0200 Subject: [PATCH 39/45] renamed Capability->Capabilities makes more sense --- pkg/ows/capabilities.go | 4 ++-- pkg/ows/operationrequest.go | 2 +- pkg/wcs201/capabilities/capabilities.go | 8 ++++---- pkg/wcs201/request/getcapabilities.go | 2 +- pkg/wcs201/response/getcapabilities.go | 2 +- pkg/wfs200/capabilities/capabilities.go | 8 ++++---- pkg/wfs200/request/describefeaturetype.go | 2 +- pkg/wfs200/request/getcapabilities.go | 2 +- pkg/wfs200/request/getfeature.go | 4 ++-- pkg/wfs200/response/getcapabilities.go | 2 +- pkg/wms130/capabilities/capabilities.go | 12 ++++++------ pkg/wms130/capabilities/capabilities_test.go | 2 +- pkg/wms130/request/getcapabilities.go | 2 +- pkg/wms130/request/getfeatureinfo.go | 4 ++-- pkg/wms130/request/getmap.go | 8 ++++---- pkg/wms130/request/getmap_test.go | 6 +++--- pkg/wms130/response/getcapabilities.go | 8 ++++---- 17 files changed, 39 insertions(+), 39 deletions(-) diff --git a/pkg/ows/capabilities.go b/pkg/ows/capabilities.go index e3f9573..011763c 100644 --- a/pkg/ows/capabilities.go +++ b/pkg/ows/capabilities.go @@ -1,11 +1,11 @@ package ows -// Capability interface +// Capabilities interface // return an error, if needed, not a Exception // because this isn't a true OWS object only a base // from which GetCapabilities can build and OperationRequest // and OperationResponse can be validated against -type Capability interface { +type Capabilities interface { ParseXML([]byte) error ParseYAMl([]byte) error } diff --git a/pkg/ows/operationrequest.go b/pkg/ows/operationrequest.go index 7a364c1..6584104 100644 --- a/pkg/ows/operationrequest.go +++ b/pkg/ows/operationrequest.go @@ -5,7 +5,7 @@ import "net/url" // OperationRequest interface type OperationRequest interface { Type() string - Validate(Capability) Exceptions + Validate(Capabilities) Exceptions ParseXML([]byte) Exceptions ParseKVP(url.Values) Exceptions diff --git a/pkg/wcs201/capabilities/capabilities.go b/pkg/wcs201/capabilities/capabilities.go index c16ba4c..36a92d5 100644 --- a/pkg/wcs201/capabilities/capabilities.go +++ b/pkg/wcs201/capabilities/capabilities.go @@ -1,17 +1,17 @@ package capabilities // ParseXML func -func (c *Capability) ParseXML(doc []byte) error { +func (c *Capabilities) ParseXML(doc []byte) error { return nil } // ParseYAMl func -func (c *Capability) ParseYAMl(doc []byte) error { +func (c *Capabilities) ParseYAMl(doc []byte) error { return nil } -// Capability struct -type Capability struct { +// Capabilities struct +type Capabilities struct { OperationsMetadata OperationsMetadata `xml:"ows:OperationsMetadata" yaml:"operationsmetadata"` ServiceMetadata ServiceMetadata `xml:"wcs:ServiceMetadata" yaml:"servicemetadata"` Contents Contents `xml:"wcs:Contents" yaml:"contents"` diff --git a/pkg/wcs201/request/getcapabilities.go b/pkg/wcs201/request/getcapabilities.go index 974563c..e856617 100644 --- a/pkg/wcs201/request/getcapabilities.go +++ b/pkg/wcs201/request/getcapabilities.go @@ -34,7 +34,7 @@ func (gc *GetCapabilities) Type() string { } // Validate validates the GetCapabilities struct -func (gc *GetCapabilities) Validate(c capabilities.Capability) ows.Exceptions { +func (gc *GetCapabilities) Validate(c capabilities.Capabilities) ows.Exceptions { return nil } diff --git a/pkg/wcs201/response/getcapabilities.go b/pkg/wcs201/response/getcapabilities.go index ce7f332..9db6e61 100644 --- a/pkg/wcs201/response/getcapabilities.go +++ b/pkg/wcs201/response/getcapabilities.go @@ -54,7 +54,7 @@ type GetCapabilities struct { Namespaces `yaml:"namespaces"` ServiceIdentification ServiceIdentification `xml:"ows:ServiceIdentification" yaml:"serviceidentification"` ServiceProvider ServiceProvider `xml:"ows:ServiceProvider" yaml:"serviceprovider"` - capabilities.Capability + capabilities.Capabilities } // Namespaces struct containing the namespaces needed for the XML document diff --git a/pkg/wfs200/capabilities/capabilities.go b/pkg/wfs200/capabilities/capabilities.go index 3b44fbb..5ab01fe 100644 --- a/pkg/wfs200/capabilities/capabilities.go +++ b/pkg/wfs200/capabilities/capabilities.go @@ -7,17 +7,17 @@ import ( ) // ParseXML func -func (c *Capability) ParseXML(doc []byte) error { +func (c *Capabilities) ParseXML(doc []byte) error { return nil } // ParseYAMl func -func (c *Capability) ParseYAMl(doc []byte) error { +func (c *Capabilities) ParseYAMl(doc []byte) error { return nil } -// Capability struct -type Capability struct { +// Capabilities struct +type Capabilities struct { OperationsMetadata OperationsMetadata `xml:"ows:OperationsMetadata" yaml:"operationsmetadata"` FeatureTypeList FeatureTypeList `xml:"wfs:FeatureTypeList" yaml:"featuretypelist"` FilterCapabilities FilterCapabilities `xml:"fes:Filter_Capabilities" yaml:"filtercapabilities"` diff --git a/pkg/wfs200/request/describefeaturetype.go b/pkg/wfs200/request/describefeaturetype.go index b50844d..89ad907 100644 --- a/pkg/wfs200/request/describefeaturetype.go +++ b/pkg/wfs200/request/describefeaturetype.go @@ -24,7 +24,7 @@ func (dft *DescribeFeatureType) Type() string { } // Validate returns GetCapabilities -func (dft *DescribeFeatureType) Validate(c capabilities.Capability) ows.Exceptions { +func (dft *DescribeFeatureType) Validate(c capabilities.Capabilities) ows.Exceptions { return nil } diff --git a/pkg/wfs200/request/getcapabilities.go b/pkg/wfs200/request/getcapabilities.go index f642da0..e5b8f9e 100644 --- a/pkg/wfs200/request/getcapabilities.go +++ b/pkg/wfs200/request/getcapabilities.go @@ -23,7 +23,7 @@ func (gc *GetCapabilities) Type() string { } // Validate returns GetCapabilities -func (gc *GetCapabilities) Validate(c capabilities.Capability) ows.Exceptions { +func (gc *GetCapabilities) Validate(c capabilities.Capabilities) ows.Exceptions { return nil } diff --git a/pkg/wfs200/request/getfeature.go b/pkg/wfs200/request/getfeature.go index 3e54ec7..97055ec 100644 --- a/pkg/wfs200/request/getfeature.go +++ b/pkg/wfs200/request/getfeature.go @@ -48,9 +48,9 @@ func (gf *GetFeature) Type() string { } // Validate returns GetFeature -func (gf *GetFeature) Validate(c ows.Capability) ows.Exceptions { +func (gf *GetFeature) Validate(c ows.Capabilities) ows.Exceptions { - //getfeaturecap := c.(capabilities.Capability) + //getfeaturecap := c.(capabilities.Capabilities) return nil } diff --git a/pkg/wfs200/response/getcapabilities.go b/pkg/wfs200/response/getcapabilities.go index e3579b3..83cd06b 100644 --- a/pkg/wfs200/response/getcapabilities.go +++ b/pkg/wfs200/response/getcapabilities.go @@ -54,7 +54,7 @@ type GetCapabilities struct { Namespaces `yaml:"namespaces"` ServiceIdentification ServiceIdentification `xml:"ows:ServiceIdentification" yaml:"serviceidentification"` ServiceProvider ServiceProvider `xml:"ows:ServiceProvider" yaml:"serviceprovider"` - capabilities.Capability + capabilities.Capabilities } // Namespaces struct containing the namespaces needed for the XML document diff --git a/pkg/wms130/capabilities/capabilities.go b/pkg/wms130/capabilities/capabilities.go index 9a77a8d..3ee082e 100644 --- a/pkg/wms130/capabilities/capabilities.go +++ b/pkg/wms130/capabilities/capabilities.go @@ -3,17 +3,17 @@ package capabilities import "github.com/pdok/ogc-specifications/pkg/ows" // ParseXML func -func (c Capability) ParseXML(doc []byte) error { +func (c Capabilities) ParseXML(doc []byte) error { return nil } // ParseYAMl func -func (c Capability) ParseYAMl(doc []byte) error { +func (c Capabilities) ParseYAMl(doc []byte) error { return nil } -// Capability struct needed for keeping all constraints and capabilities together -type Capability struct { +// Capabilities struct needed for keeping all constraints and capabilities together +type Capabilities struct { WMSCapabilities OptionalConstraints } @@ -69,7 +69,7 @@ type Layer struct { } // StyleDefined checks if the style that is defined is available for the requested layer -func (c *Capability) StyleDefined(layername, stylename string) bool { +func (c *Capabilities) StyleDefined(layername, stylename string) bool { defined := false for _, layer := range c.Layer { defined = layer.styleDefined(layername, stylename) @@ -108,7 +108,7 @@ func (l *Layer) styleDefined(layername, stylename string) bool { } // GetLayerNames returns the available layers as []string -func (c *Capability) GetLayerNames() []string { +func (c *Capabilities) GetLayerNames() []string { var layers []string for _, l := range c.Layer { diff --git a/pkg/wms130/capabilities/capabilities_test.go b/pkg/wms130/capabilities/capabilities_test.go index 5907c42..86e43e7 100644 --- a/pkg/wms130/capabilities/capabilities_test.go +++ b/pkg/wms130/capabilities/capabilities_test.go @@ -8,7 +8,7 @@ func sp(s string) *string { return &s } -var capabilities = Capability{ +var capabilities = Capabilities{ WMSCapabilities: WMSCapabilities{ Layer: []Layer{ {Name: sp(`depthOneLayerOne`), diff --git a/pkg/wms130/request/getcapabilities.go b/pkg/wms130/request/getcapabilities.go index 84346c6..f947fbe 100644 --- a/pkg/wms130/request/getcapabilities.go +++ b/pkg/wms130/request/getcapabilities.go @@ -20,7 +20,7 @@ func (gc *GetCapabilities) Type() string { } // Validate returns GetCapabilities -func (gc *GetCapabilities) Validate(c ows.Capability) ows.Exceptions { +func (gc *GetCapabilities) Validate(c ows.Capabilities) ows.Exceptions { var exceptions ows.Exceptions return exceptions } diff --git a/pkg/wms130/request/getfeatureinfo.go b/pkg/wms130/request/getfeatureinfo.go index a68eee8..4b52761 100644 --- a/pkg/wms130/request/getfeatureinfo.go +++ b/pkg/wms130/request/getfeatureinfo.go @@ -36,10 +36,10 @@ func (gfi *GetFeatureInfo) Type() string { } // Validate returns GetFeatureInfo -func (gfi *GetFeatureInfo) Validate(c ows.Capability) ows.Exceptions { +func (gfi *GetFeatureInfo) Validate(c ows.Capabilities) ows.Exceptions { var exceptions ows.Exceptions - wmsCapabilities := c.(capabilities.Capability) + wmsCapabilities := c.(capabilities.Capabilities) exceptions = append(exceptions, gfi.StyledLayerDescriptor.Validate(wmsCapabilities)...) // exceptions = append(exceptions, gfi.Output.Validate(wmsCapabilities)...) diff --git a/pkg/wms130/request/getmap.go b/pkg/wms130/request/getmap.go index bdfb2c8..a3089ce 100644 --- a/pkg/wms130/request/getmap.go +++ b/pkg/wms130/request/getmap.go @@ -44,10 +44,10 @@ func (gm *GetMap) Type() string { } // Validate returns GetMap -func (gm *GetMap) Validate(c ows.Capability) ows.Exceptions { +func (gm *GetMap) Validate(c ows.Capabilities) ows.Exceptions { var exceptions ows.Exceptions - wmsCapabilities := c.(capabilities.Capability) + wmsCapabilities := c.(capabilities.Capabilities) exceptions = append(exceptions, gm.StyledLayerDescriptor.Validate(wmsCapabilities)...) exceptions = append(exceptions, gm.Output.Validate(wmsCapabilities)...) @@ -246,7 +246,7 @@ type GetMap struct { } // Validate validates the output parameters -func (output *Output) Validate(c capabilities.Capability) ows.Exceptions { +func (output *Output) Validate(c capabilities.Capabilities) ows.Exceptions { var exceptions ows.Exceptions if output.Size.Width > c.MaxWidth { exceptions = append(exceptions, ows.NoApplicableCode(fmt.Sprintf("Image size out of range, WIDTH must be between 1 and %d pixels", c.MaxWidth))) @@ -292,7 +292,7 @@ type StyledLayerDescriptor struct { } // Validate the StyledLayerDescriptor -func (sld *StyledLayerDescriptor) Validate(c capabilities.Capability) ows.Exceptions { +func (sld *StyledLayerDescriptor) Validate(c capabilities.Capabilities) ows.Exceptions { var unknownLayers []string var unknownStyles []struct{ layer, style string } diff --git a/pkg/wms130/request/getmap_test.go b/pkg/wms130/request/getmap_test.go index e056bc0..ba2ad92 100644 --- a/pkg/wms130/request/getmap_test.go +++ b/pkg/wms130/request/getmap_test.go @@ -68,12 +68,12 @@ func TestBuildStyledLayerDescriptor(t *testing.T) { func TestValidateStyledLayerDescriptor(t *testing.T) { var tests = []struct { - capabilities capabilities.Capability + capabilities capabilities.Capabilities sld StyledLayerDescriptor exceptions ows.Exceptions }{ 0: { - capabilities: capabilities.Capability{ + capabilities: capabilities.Capabilities{ WMSCapabilities: capabilities.WMSCapabilities{ Layer: []capabilities.Layer{ {Name: sp(`layer1`)}, @@ -84,7 +84,7 @@ func TestValidateStyledLayerDescriptor(t *testing.T) { sld: StyledLayerDescriptor{NamedLayer: []NamedLayer{{Name: "layer1", NamedStyle: &NamedStyle{Name: ``}}, {Name: "layer2", NamedStyle: &NamedStyle{Name: `styleone`}}}}, }, 1: { - capabilities: capabilities.Capability{ + capabilities: capabilities.Capabilities{ WMSCapabilities: capabilities.WMSCapabilities{ Layer: []capabilities.Layer{ {Name: sp(`layer2`), Style: []*capabilities.Style{{Name: `styleone`}}}, diff --git a/pkg/wms130/response/getcapabilities.go b/pkg/wms130/response/getcapabilities.go index 3a6159d..475836a 100644 --- a/pkg/wms130/response/getcapabilities.go +++ b/pkg/wms130/response/getcapabilities.go @@ -50,10 +50,10 @@ func (gc *GetCapabilities) BuildXML() []byte { // GetCapabilities base struct type GetCapabilities struct { - XMLName xml.Name `xml:"WMS_Capabilities"` - Namespaces `yaml:"namespaces"` - WMSService WMSService `xml:"Service" yaml:"service"` - Capability capabilities.Capability `xml:"Capability" yaml:"capability"` + XMLName xml.Name `xml:"WMS_Capabilities"` + Namespaces `yaml:"namespaces"` + WMSService WMSService `xml:"Service" yaml:"service"` + Capabilities capabilities.Capabilities `xml:"Capability" yaml:"capability"` } // Namespaces struct containing the namespaces needed for the XML document From 0399c07c242d5724557bf45fe5f4cc1b55bcd63a Mon Sep 17 00:00:00 2001 From: Wouter Date: Fri, 7 Aug 2020 16:59:29 +0200 Subject: [PATCH 40/45] func->private, validation, espg.Code string->int --- go.mod | 2 + go.sum | 4 + pkg/ows/common.go | 11 +- pkg/ows/xml.go | 4 +- pkg/wms130/capabilities/capabilities.go | 70 +++++++- pkg/wms130/capabilities/capabilities_test.go | 29 ++++ pkg/wms130/request/getfeatureinfo.go | 2 +- pkg/wms130/request/getmap.go | 29 ++-- pkg/wms130/request/getmap_test.go | 163 ++++++++++++++++++- pkg/wms130/request/getmapkvp.go | 4 +- 10 files changed, 289 insertions(+), 29 deletions(-) diff --git a/go.mod b/go.mod index a1e29a0..a025d98 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,5 @@ module github.com/pdok/ogc-specifications go 1.14 + +require gopkg.in/yaml.v2 v2.3.0 diff --git a/go.sum b/go.sum index e69de29..168980d 100644 --- a/go.sum +++ b/go.sum @@ -0,0 +1,4 @@ +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/pkg/ows/common.go b/pkg/ows/common.go index ebc3afe..6fee006 100644 --- a/pkg/ows/common.go +++ b/pkg/ows/common.go @@ -92,17 +92,17 @@ func StripDuplicateAttr(attr []xml.Attr) []xml.Attr { // CRS struct with namespace/authority/registry and code type CRS struct { Namespace string //TODO maybe AuthorityType is a better name...? - Code string + Code int } // String of the EPSGCode func (c *CRS) String() string { - return c.Namespace + `:` + c.Code + return c.Namespace + `:` + strconv.Itoa(c.Code) } // Identifier returns the EPSG func (c *CRS) Identifier() string { - return codeSpace + c.Code + return codeSpace + strconv.Itoa(c.Code) } // ParseString build CRS struct from input string @@ -115,6 +115,9 @@ func (c *CRS) parseString(s string) { code := regex.FindStringSubmatch(s) if len(code) == 3 { // code[0] is the full match, the other the parts c.Namespace = code[1] - c.Code = code[2] + + // the regex already checks if it [0-9] + i, _ := strconv.Atoi(code[2]) + c.Code = i } } diff --git a/pkg/ows/xml.go b/pkg/ows/xml.go index caa1ae6..96a1c24 100644 --- a/pkg/ows/xml.go +++ b/pkg/ows/xml.go @@ -67,8 +67,8 @@ func (p *Position) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { // MarshalXML Position func (c *CRS) MarshalXML(e *xml.Encoder, start xml.StartElement) error { var s = `` - if c.Namespace != `` && c.Code != `` { - s = fmt.Sprintf("%s:%s", c.Namespace, c.Code) + if c.Namespace != `` { + s = fmt.Sprintf("%s:%d", c.Namespace, c.Code) } return e.EncodeElement(s, start) diff --git a/pkg/wms130/capabilities/capabilities.go b/pkg/wms130/capabilities/capabilities.go index 3ee082e..8c7d0af 100644 --- a/pkg/wms130/capabilities/capabilities.go +++ b/pkg/wms130/capabilities/capabilities.go @@ -1,14 +1,29 @@ package capabilities -import "github.com/pdok/ogc-specifications/pkg/ows" +import ( + "encoding/xml" + "log" + + "github.com/pdok/ogc-specifications/pkg/ows" + "github.com/pdok/ogc-specifications/pkg/wms130/exception" + "gopkg.in/yaml.v2" +) // ParseXML func func (c Capabilities) ParseXML(doc []byte) error { + if err := xml.Unmarshal(doc, &c); err != nil { + log.Fatalf("error: %v", err) + return err + } return nil } // ParseYAMl func func (c Capabilities) ParseYAMl(doc []byte) error { + if err := yaml.Unmarshal(doc, &c); err != nil { + log.Fatalf("error: %v", err) + return err + } return nil } @@ -58,7 +73,7 @@ type Layer struct { Title string `xml:"Title" yaml:"title"` Abstract string `xml:"Abstract" yaml:"abstract"` KeywordList *ows.Keywords `xml:"KeywordList" yaml:"keywordlist"` - CRS []*string `xml:"CRS" yaml:"crs"` + CRS []ows.CRS `xml:"CRS" yaml:"crs"` EXGeographicBoundingBox *EXGeographicBoundingBox `xml:"EX_GeographicBoundingBox" yaml:"exgeographicboundingbox"` BoundingBox []*BoundingBox `xml:"BoundingBox" yaml:"boundingbox"` AuthorityURL *AuthorityURL `xml:"AuthorityURL" yaml:"authorityurl"` @@ -139,6 +154,57 @@ func (l *Layer) getLayerNames() []string { return layers } +func (l *Layer) findLayer(layername string) *Layer { + if *l.Name == layername { + return l + } + if l.Layer != nil { + for _, n := range l.Layer { + u := n.findLayer(layername) + if u != nil { + if *u.Name == layername { + return u + } + } + } + } + return nil +} + +// GetLayer returns the Layer Capabilities from the Capabilities document. +// when the requested Layer is not found a exception is thrown. +func (c *Capabilities) GetLayer(layername string) (Layer, ows.Exception) { + var layer Layer + + found := false + for _, l := range c.GetLayerNames() { + if l == layername { + found = true + } + } + + if !found { + return layer, exception.LayerNotDefined(layername) + } + + for _, l := range c.Layer { + if *l.Name == layername { + layer = l + break + } + if l.Layer != nil { + for _, n := range l.Layer { + u := n.findLayer(layername) + if u != nil { + return *u, nil + } + } + } + } + + return layer, nil +} + // RequestType containing the formats and DCPTypes available type RequestType struct { Format []string `xml:"Format" yaml:"format"` diff --git a/pkg/wms130/capabilities/capabilities_test.go b/pkg/wms130/capabilities/capabilities_test.go index 86e43e7..ffb3ad0 100644 --- a/pkg/wms130/capabilities/capabilities_test.go +++ b/pkg/wms130/capabilities/capabilities_test.go @@ -2,6 +2,9 @@ package capabilities import ( "testing" + + "github.com/pdok/ogc-specifications/pkg/ows" + "github.com/pdok/ogc-specifications/pkg/wms130/exception" ) func sp(s string) *string { @@ -64,3 +67,29 @@ func TestStyleDefined(t *testing.T) { } } } + +func TestGetLayer(t *testing.T) { + var tests = []struct { + layername string + exception ows.Exception + }{ + 0: {layername: `depthTwoLayerThree`}, + 1: {layername: `depthThreeLayerSeven`}, + 2: {layername: `unknownLayer`, exception: exception.LayerNotDefined(`unknownLayer`)}, + } + + for k, test := range tests { + layerfound, exception := capabilities.GetLayer(test.layername) + if exception != nil { + if test.exception != nil { + if test.exception.Code() != exception.Code() { + t.Errorf("test: %d, expected: %s \ngot: %v", k, test.layername, capabilities.GetLayerNames()) + } + } + } else { + if *layerfound.Name != test.layername { + t.Errorf("test: %d, expected: %s \ngot: %s", k, capabilities.GetLayerNames(), *layerfound.Name) + } + } + } +} diff --git a/pkg/wms130/request/getfeatureinfo.go b/pkg/wms130/request/getfeatureinfo.go index 4b52761..6c3abf8 100644 --- a/pkg/wms130/request/getfeatureinfo.go +++ b/pkg/wms130/request/getfeatureinfo.go @@ -80,7 +80,7 @@ func (gfi *GetFeatureInfo) ParseOperationRequestKVP(orkvp ows.OperationRequestKV gfi.XMLName.Local = getfeatureinfo gfi.BaseRequest.Build(gfikvp.Service, gfikvp.Version) - sld, ex := gfikvp.BuildStyledLayerDescriptor() + sld, ex := gfikvp.buildStyledLayerDescriptor() if ex != nil { return ows.Exceptions{ex} } diff --git a/pkg/wms130/request/getmap.go b/pkg/wms130/request/getmap.go index a3089ce..8af8cc6 100644 --- a/pkg/wms130/request/getmap.go +++ b/pkg/wms130/request/getmap.go @@ -52,18 +52,25 @@ func (gm *GetMap) Validate(c ows.Capabilities) ows.Exceptions { exceptions = append(exceptions, gm.StyledLayerDescriptor.Validate(wmsCapabilities)...) exceptions = append(exceptions, gm.Output.Validate(wmsCapabilities)...) + for _, sld := range gm.StyledLayerDescriptor.NamedLayer { + layer, exception := wmsCapabilities.GetLayer(sld.Name) + if exception != nil { + exceptions = append(exceptions, exception) + } + exceptions = append(exceptions, checkCRS(gm.CRS, layer.CRS)) + } + return exceptions } -// CheckCRS against a given list of CRS -func CheckCRS(crs string, definedCrs []string) ows.Exception { +// checkCRS against a given list of CRS +func checkCRS(crs ows.CRS, definedCrs []ows.CRS) ows.Exception { for _, defined := range definedCrs { if defined == crs { return nil } } - - return exception.InvalidCRS(crs) + return exception.InvalidCRS(crs.String()) } // ParseOperationRequestKVP process the simple struct to a complex struct @@ -73,7 +80,7 @@ func (gm *GetMap) ParseOperationRequestKVP(orkvp ows.OperationRequestKVP) ows.Ex gm.XMLName.Local = getmap gm.BaseRequest.Build(gmkvp.Service, gmkvp.Version) - sld, err := gmkvp.BuildStyledLayerDescriptor() + sld, err := gmkvp.buildStyledLayerDescriptor() if err != nil { return ows.Exceptions{err} } @@ -89,7 +96,7 @@ func (gm *GetMap) ParseOperationRequestKVP(orkvp ows.OperationRequestKVP) ows.Ex } gm.BoundingBox = bbox - output, err := gmkvp.BuildOutput() + output, err := gmkvp.buildOutput() if err != nil { return ows.Exceptions{err} } @@ -197,25 +204,24 @@ func buildStyledLayerDescriptor(layers, styles []string) (StyledLayerDescriptor, // TODO maybe 'merge' both func in a single one with 2 outputs // so their are 'in sync' ...? func (sld *StyledLayerDescriptor) getLayerKVPValue() string { - return strings.Join(sld.GetNamedLayers(), ",") + return strings.Join(sld.getNamedLayers(), ",") } func (sld *StyledLayerDescriptor) getStyleKVPValue() string { - return strings.Join(sld.GetNamedStyles(), ",") + return strings.Join(sld.getNamedStyles(), ",") } // GetNamedLayers return an array of the Layer names -func (sld *StyledLayerDescriptor) GetNamedLayers() []string { +func (sld *StyledLayerDescriptor) getNamedLayers() []string { layers := []string{} for _, l := range sld.NamedLayer { layers = append(layers, l.Name) } - return layers } // GetNamedStyles return an array of the Layer names -func (sld *StyledLayerDescriptor) GetNamedStyles() []string { +func (sld *StyledLayerDescriptor) getNamedStyles() []string { styles := []string{} for _, l := range sld.NamedLayer { if l.Name != "" { @@ -226,7 +232,6 @@ func (sld *StyledLayerDescriptor) GetNamedStyles() []string { } } } - return styles } diff --git a/pkg/wms130/request/getmap_test.go b/pkg/wms130/request/getmap_test.go index ba2ad92..645ddfc 100644 --- a/pkg/wms130/request/getmap_test.go +++ b/pkg/wms130/request/getmap_test.go @@ -179,7 +179,7 @@ func TestGetMapParseXML(t *testing.T) { {Name: "Roads", NamedStyle: &NamedStyle{Name: "CenterLine"}}, {Name: "Houses", NamedStyle: &NamedStyle{Name: "Outline"}}, }}, - CRS: ows.CRS{Namespace: "EPSG", Code: "4326"}, + CRS: ows.CRS{Namespace: "EPSG", Code: 4326}, BoundingBox: ows.BoundingBox{ Crs: "http://www.opengis.net/gml/srs/epsg.xml#4326", LowerCorner: [2]float64{-180.0, -90.0}, @@ -303,7 +303,7 @@ func TestGetMapParseKVP(t *testing.T) { {Name: "Roads", NamedStyle: &NamedStyle{Name: "CenterLine"}}, {Name: "Houses", NamedStyle: &NamedStyle{Name: "Outline"}}, }}, - CRS: ows.CRS{Namespace: "EPSG", Code: "4326"}, + CRS: ows.CRS{Namespace: "EPSG", Code: 4326}, BoundingBox: ows.BoundingBox{ LowerCorner: [2]float64{-180.0, -90.0}, UpperCorner: [2]float64{180.0, 90.0}, @@ -347,7 +347,7 @@ func TestGetMapBuildKVP(t *testing.T) { {Name: "Roads", NamedStyle: &NamedStyle{Name: "CenterLine"}}, {Name: "Houses", NamedStyle: &NamedStyle{Name: "Outline"}}, }}, - CRS: ows.CRS{Namespace: "EPSG", Code: "4326"}, + CRS: ows.CRS{Namespace: "EPSG", Code: 4326}, BoundingBox: ows.BoundingBox{ LowerCorner: [2]float64{-180.0, -90.0}, UpperCorner: [2]float64{180.0, 90.0}, @@ -372,7 +372,7 @@ func TestGetMapBuildKVP(t *testing.T) { SERVICE: {`WMS`}, }}, 1: {Object: GetMap{ - CRS: ows.CRS{Namespace: "EPSG", Code: "4326"}, + CRS: ows.CRS{Namespace: "EPSG", Code: 4326}, BoundingBox: ows.BoundingBox{ LowerCorner: [2]float64{-180.0, -90.0}, UpperCorner: [2]float64{180.0, 90.0}, @@ -449,6 +449,63 @@ func TestGetMapBuildXML(t *testing.T) { } +func TestGetNamedStyles(t *testing.T) { + var tests = []struct { + sld StyledLayerDescriptor + styles []string + }{ + 0: {sld: StyledLayerDescriptor{NamedLayer: []NamedLayer{{Name: "layer1", NamedStyle: &NamedStyle{Name: "style1"}}, {Name: "layer2", NamedStyle: &NamedStyle{Name: "style2"}}}}, + styles: []string{"style1", "style2"}}, + 1: {sld: StyledLayerDescriptor{NamedLayer: []NamedLayer{{Name: "layer1"}, {Name: "layer2"}}}, + styles: []string{"", ""}}, + 2: {sld: StyledLayerDescriptor{NamedLayer: []NamedLayer{{Name: "layer1", NamedStyle: &NamedStyle{Name: "style1"}}, {Name: "layer2"}, {Name: "layer3", NamedStyle: &NamedStyle{Name: "style3"}}}}, + styles: []string{"style1", "", "style3"}}, + } + + for k, test := range tests { + styleslist := test.sld.getNamedStyles() + if len(styleslist) != len(test.styles) { + t.Errorf("test: %d, Expected %s but was not \n got: %s", k, test.styles, styleslist) + } else { + for _, style := range styleslist { + found := false + for _, expected := range test.styles { + if expected == style { + found = true + } + } + if !found { + t.Errorf("test: %d, Expected %s but was not \n got: %s", k, test.styles, style) + } + } + } + } +} + +func TestCheckCRS(t *testing.T) { + definedCrs := []ows.CRS{{Namespace: `CRS`, Code: 84}, {Namespace: `EPSG`, Code: 4326}, {Namespace: `EPSG`, Code: 3857}} + var tests = []struct { + crs ows.CRS + exception ows.Exception + }{ + 0: {crs: ows.CRS{Namespace: `CRS`, Code: 84}}, + 1: {crs: ows.CRS{Namespace: `UNKNOWN`}, exception: exception.InvalidCRS(`UNKNOWN`)}, + } + + for k, test := range tests { + exception := checkCRS(test.crs, definedCrs) + if exception != nil { + if test.exception != nil { + if exception.Code() != test.exception.Code() { + t.Errorf("test: %d, Expected one of %s but was not \n got: %s", k, test.exception.Code(), exception.Code()) + } + } else { + t.Errorf("test: %d, Expected one of %v but was not \n got: %s", k, definedCrs, test.crs.String()) + } + } + } +} + func compareGetMapObject(result, expected GetMap, t *testing.T, k int) { if result.BaseRequest.Version != expected.BaseRequest.Version { t.Errorf("test Version: %d, expected: %s ,\n got: %s", k, expected.Version, result.Version) @@ -509,6 +566,59 @@ func compareGetMapObject(result, expected GetMap, t *testing.T, k int) { } } +// ---------- +// Validation +// ---------- + +func TestGetMapValidate(t *testing.T) { + capabilities := capabilities.Capabilities{} + + var tests = []struct { + gm GetMap + exceptions ows.Exceptions + }{ + 0: {gm: GetMap{ + BaseRequest: BaseRequest{ + Version: "1.3.0", + Attr: ows.XMLAttribute{ + xml.Attr{Name: xml.Name{Local: "xmlns"}, Value: "http://www.opengis.net/sld"}, + xml.Attr{Name: xml.Name{Space: "xmlns", Local: "gml"}, Value: "http://www.opengis.net/gml"}, + xml.Attr{Name: xml.Name{Space: "xmlns", Local: "ogc"}, Value: "http://www.opengis.net/ogc"}, + xml.Attr{Name: xml.Name{Space: "xmlns", Local: "ows"}, Value: "http://www.opengis.net/ows"}, + xml.Attr{Name: xml.Name{Space: "xmlns", Local: "se"}, Value: "http://www.opengis.net/se"}, + xml.Attr{Name: xml.Name{Space: "xmlns", Local: "wms"}, Value: "http://www.opengis.net/wms"}, + xml.Attr{Name: xml.Name{Space: "xmlns", Local: "xsi"}, Value: "http://www.w3.org/2001/XMLSchema-instance"}, + xml.Attr{Name: xml.Name{Space: "http://www.w3.org/2001/XMLSchema-instance", Local: "schemaLocation"}, Value: "http://www.opengis.net/sld GetMap.xsd"}, + }}, + StyledLayerDescriptor: StyledLayerDescriptor{ + Version: "1.1.0", + NamedLayer: []NamedLayer{ + {Name: "Rivers", NamedStyle: &NamedStyle{Name: "CenterLine"}}, + {Name: "Roads", NamedStyle: &NamedStyle{Name: "CenterLine"}}, + {Name: "Houses", NamedStyle: &NamedStyle{Name: "Outline"}}, + }}, + CRS: ows.CRS{Namespace: "EPSG", Code: 4326}, + BoundingBox: ows.BoundingBox{ + Crs: "http://www.opengis.net/gml/srs/epsg.xml#4326", + LowerCorner: [2]float64{-180.0, -90.0}, + UpperCorner: [2]float64{180.0, 90.0}, + }, + Output: Output{ + Size: Size{Width: 1024, Height: 512}, + Format: "image/jpeg", + Transparent: bp(false)}, + Exceptions: sp("XML"), + }}, + } + + for k, test := range tests { + exceptions := test.gm.Validate(capabilities) + if exceptions != nil { + t.Errorf("test CRS: %d, expected: %v+ ,\n got: %v+", k, exceptions, test.exceptions) + } + } +} + // ---------- // Benchmarks // ---------- @@ -534,7 +644,7 @@ func BenchmarkGetMapBuildKVP(b *testing.B) { {Name: "Roads", NamedStyle: &NamedStyle{Name: "CenterLine"}}, {Name: "Houses", NamedStyle: &NamedStyle{Name: "Outline"}}, }}, - CRS: ows.CRS{Namespace: "EPSG", Code: "4326"}, + CRS: ows.CRS{Namespace: "EPSG", Code: 4326}, BoundingBox: ows.BoundingBox{ Crs: "http://www.opengis.net/gml/srs/epsg.xml#4326", LowerCorner: [2]float64{-180.0, -90.0}, @@ -572,7 +682,7 @@ func BenchmarkGetMapBuildXML(b *testing.B) { {Name: "Roads", NamedStyle: &NamedStyle{Name: "CenterLine"}}, {Name: "Houses", NamedStyle: &NamedStyle{Name: "Outline"}}, }}, - CRS: ows.CRS{Namespace: "EPSG", Code: "4326"}, + CRS: ows.CRS{Namespace: "EPSG", Code: 4326}, BoundingBox: ows.BoundingBox{ Crs: "http://www.opengis.net/gml/srs/epsg.xml#4326", LowerCorner: [2]float64{-180.0, -90.0}, @@ -653,3 +763,44 @@ func BenchmarkGetMapParseXML(b *testing.B) { gm.ParseXML(doc) } } + +func BenchmarkGetMapValidate(b *testing.B) { + capabilities := capabilities.Capabilities{} + + gm := GetMap{ + BaseRequest: BaseRequest{ + Version: "1.3.0", + Attr: ows.XMLAttribute{ + xml.Attr{Name: xml.Name{Local: "xmlns"}, Value: "http://www.opengis.net/sld"}, + xml.Attr{Name: xml.Name{Space: "xmlns", Local: "gml"}, Value: "http://www.opengis.net/gml"}, + xml.Attr{Name: xml.Name{Space: "xmlns", Local: "ogc"}, Value: "http://www.opengis.net/ogc"}, + xml.Attr{Name: xml.Name{Space: "xmlns", Local: "ows"}, Value: "http://www.opengis.net/ows"}, + xml.Attr{Name: xml.Name{Space: "xmlns", Local: "se"}, Value: "http://www.opengis.net/se"}, + xml.Attr{Name: xml.Name{Space: "xmlns", Local: "wms"}, Value: "http://www.opengis.net/wms"}, + xml.Attr{Name: xml.Name{Space: "xmlns", Local: "xsi"}, Value: "http://www.w3.org/2001/XMLSchema-instance"}, + xml.Attr{Name: xml.Name{Space: "http://www.w3.org/2001/XMLSchema-instance", Local: "schemaLocation"}, Value: "http://www.opengis.net/sld GetMap.xsd"}, + }}, + StyledLayerDescriptor: StyledLayerDescriptor{ + Version: "1.1.0", + NamedLayer: []NamedLayer{ + {Name: "Rivers", NamedStyle: &NamedStyle{Name: "CenterLine"}}, + {Name: "Roads", NamedStyle: &NamedStyle{Name: "CenterLine"}}, + {Name: "Houses", NamedStyle: &NamedStyle{Name: "Outline"}}, + }}, + CRS: ows.CRS{Namespace: "EPSG", Code: 4326}, + BoundingBox: ows.BoundingBox{ + Crs: "http://www.opengis.net/gml/srs/epsg.xml#4326", + LowerCorner: [2]float64{-180.0, -90.0}, + UpperCorner: [2]float64{180.0, 90.0}, + }, + Output: Output{ + Size: Size{Width: 1024, Height: 512}, + Format: "image/jpeg", + Transparent: bp(false)}, + Exceptions: sp("XML"), + } + + for i := 0; i < b.N; i++ { + gm.Validate(capabilities) + } +} diff --git a/pkg/wms130/request/getmapkvp.go b/pkg/wms130/request/getmapkvp.go index 12727d2..58fd90e 100644 --- a/pkg/wms130/request/getmapkvp.go +++ b/pkg/wms130/request/getmapkvp.go @@ -126,7 +126,7 @@ func (gmkvp *GetMapKVP) ParseOperationRequest(or ows.OperationRequest) ows.Excep } // BuildOutput builds a Output struct from the KVP information -func (gmkvp *GetMapKVP) BuildOutput() (Output, ows.Exception) { +func (gmkvp *GetMapKVP) buildOutput() (Output, ows.Exception) { output := Output{} h, err := strconv.Atoi(gmkvp.Height) @@ -149,7 +149,7 @@ func (gmkvp *GetMapKVP) BuildOutput() (Output, ows.Exception) { } // BuildStyledLayerDescriptor builds a StyledLayerDescriptor struct from the KVP information -func (sl *StyledLayer) BuildStyledLayerDescriptor() (StyledLayerDescriptor, ows.Exception) { +func (sl *StyledLayer) buildStyledLayerDescriptor() (StyledLayerDescriptor, ows.Exception) { var layers, styles []string if sl.Layers != `` { layers = strings.Split(sl.Layers, ",") From 955e320629656e399f2f463c5d828bb6a5e19d23 Mon Sep 17 00:00:00 2001 From: Wouter Date: Thu, 13 Aug 2020 19:06:19 +0200 Subject: [PATCH 41/45] GetMapValidate test --- pkg/wfs200/capabilities/capabilities.go | 4 +- pkg/wms130/capabilities/capabilities.go | 10 +++-- pkg/wms130/request/getmap.go | 4 +- pkg/wms130/request/getmap_test.go | 59 +++++++++++++++++++++++-- 4 files changed, 66 insertions(+), 11 deletions(-) diff --git a/pkg/wfs200/capabilities/capabilities.go b/pkg/wfs200/capabilities/capabilities.go index 5ab01fe..c626136 100644 --- a/pkg/wfs200/capabilities/capabilities.go +++ b/pkg/wfs200/capabilities/capabilities.go @@ -109,8 +109,8 @@ type FeatureType struct { Title string `xml:"wfs:Title" yaml:"title"` Abstract string `xml:"wfs:Abstract" yaml:"abstract"` Keywords *ows.Keywords `xml:"ows:Keywords" yaml:"keywords"` - DefaultCRS *string `xml:"wfs:DefaultCRS" yaml:"defaultcrs"` - OtherCRS *[]string `xml:"wfs:OtherCRS" yaml:"othercrs"` + DefaultCRS *ows.CRS `xml:"wfs:DefaultCRS" yaml:"defaultcrs"` + OtherCRS *[]ows.CRS `xml:"wfs:OtherCRS" yaml:"othercrs"` OutputFormats struct { Format []string `xml:"wfs:Format" yaml:"format"` } `xml:"wfs:OutputFormats" yaml:"outputformats"` diff --git a/pkg/wms130/capabilities/capabilities.go b/pkg/wms130/capabilities/capabilities.go index 8c7d0af..8fb8689 100644 --- a/pkg/wms130/capabilities/capabilities.go +++ b/pkg/wms130/capabilities/capabilities.go @@ -64,7 +64,7 @@ type Exception struct { // Layer contains the WMS 1.3.0 layer configuration type Layer struct { - Queryable *string `xml:"queryable,attr" yaml:"queryable"` + Queryable *int `xml:"queryable,attr" yaml:"queryable"` // layer has a full/complete map coverage Opaque *string `xml:"opaque,attr" yaml:"opaque"` // no cascaded attr in Layer element, because we don't do cascaded services e.g. wms services "proxying" and/or combining other wms services @@ -98,7 +98,7 @@ func (c *Capabilities) StyleDefined(layername, stylename string) bool { // styleDefined checks if the style is defined func (l *Layer) styleDefined(layername, stylename string) bool { - if *l.Name == layername { + if l.Name != nil && *l.Name == layername { if l.Style != nil { for _, sld := range l.Style { if sld.Name == stylename { @@ -127,7 +127,9 @@ func (c *Capabilities) GetLayerNames() []string { var layers []string for _, l := range c.Layer { - layers = append(layers, *l.Name) + if l.Name != nil { + layers = append(layers, *l.Name) + } if l.Layer != nil { for _, n := range l.Layer { u := n.getLayerNames() @@ -188,7 +190,7 @@ func (c *Capabilities) GetLayer(layername string) (Layer, ows.Exception) { } for _, l := range c.Layer { - if *l.Name == layername { + if l.Name != nil && *l.Name == layername { layer = l break } diff --git a/pkg/wms130/request/getmap.go b/pkg/wms130/request/getmap.go index 8af8cc6..4863de5 100644 --- a/pkg/wms130/request/getmap.go +++ b/pkg/wms130/request/getmap.go @@ -57,7 +57,9 @@ func (gm *GetMap) Validate(c ows.Capabilities) ows.Exceptions { if exception != nil { exceptions = append(exceptions, exception) } - exceptions = append(exceptions, checkCRS(gm.CRS, layer.CRS)) + if exception := checkCRS(gm.CRS, layer.CRS); exception != nil { + exceptions = append(exceptions, exception) + } } return exceptions diff --git a/pkg/wms130/request/getmap_test.go b/pkg/wms130/request/getmap_test.go index 645ddfc..f84d8e2 100644 --- a/pkg/wms130/request/getmap_test.go +++ b/pkg/wms130/request/getmap_test.go @@ -571,7 +571,58 @@ func compareGetMapObject(result, expected GetMap, t *testing.T, k int) { // ---------- func TestGetMapValidate(t *testing.T) { - capabilities := capabilities.Capabilities{} + capabilities := capabilities.Capabilities{ + WMSCapabilities: capabilities.WMSCapabilities{ + Request: capabilities.Request{ + GetMap: capabilities.RequestType{ + Format: []string{`image/jpeg`}, + DCPType: capabilities.DCPType{}, + }, + }, + Layer: []capabilities.Layer{ + { + Queryable: ip(1), + Title: `Rivers, Roads and Houses`, + CRS: []ows.CRS{{Code: 4326, Namespace: `EPSG`}}, + Layer: []*capabilities.Layer{ + { + Queryable: ip(1), + Name: sp(`Rivers`), + Title: `Rivers`, + CRS: []ows.CRS{{Code: 4326, Namespace: `EPSG`}}, + Style: []*capabilities.Style{ + { + Name: `CenterLine`, + }, + }, + }, + { + Queryable: ip(1), + Name: sp(`Roads`), + Title: `Roads`, + CRS: []ows.CRS{{Code: 4326, Namespace: `EPSG`}}, + Style: []*capabilities.Style{ + { + Name: `CenterLine`, + }, + }, + }, + { + Queryable: ip(1), + Name: sp(`Houses`), + Title: `Houses`, + CRS: []ows.CRS{{Code: 4326, Namespace: `EPSG`}}, + Style: []*capabilities.Style{ + { + Name: `Outline`, + }, + }, + }, + }, + }, + }, + }, + } var tests = []struct { gm GetMap @@ -612,9 +663,9 @@ func TestGetMapValidate(t *testing.T) { } for k, test := range tests { - exceptions := test.gm.Validate(capabilities) - if exceptions != nil { - t.Errorf("test CRS: %d, expected: %v+ ,\n got: %v+", k, exceptions, test.exceptions) + getmapexceptions := test.gm.Validate(capabilities) + if getmapexceptions != nil { + t.Errorf("test Validation: %d, expected: %v+ ,\n got: %v+", k, test.exceptions, getmapexceptions) } } } From 44026db6044d91bc0a93fb0e231aa3d09c03871d Mon Sep 17 00:00:00 2001 From: Wouter Date: Thu, 13 Aug 2020 19:12:00 +0200 Subject: [PATCH 42/45] GetMapValidate benchmark --- pkg/wms130/request/getmap_test.go | 92 +++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/pkg/wms130/request/getmap_test.go b/pkg/wms130/request/getmap_test.go index f84d8e2..f9b1b5b 100644 --- a/pkg/wms130/request/getmap_test.go +++ b/pkg/wms130/request/getmap_test.go @@ -855,3 +855,95 @@ func BenchmarkGetMapValidate(b *testing.B) { gm.Validate(capabilities) } } + +func BenchmarkGetMapParseValidate(b *testing.B) { + capabilities := capabilities.Capabilities{ + WMSCapabilities: capabilities.WMSCapabilities{ + Request: capabilities.Request{ + GetMap: capabilities.RequestType{ + Format: []string{`image/jpeg`}, + DCPType: capabilities.DCPType{}, + }, + }, + Layer: []capabilities.Layer{ + { + Queryable: ip(1), + Title: `Rivers, Roads and Houses`, + CRS: []ows.CRS{{Code: 4326, Namespace: `EPSG`}}, + Layer: []*capabilities.Layer{ + { + Queryable: ip(1), + Name: sp(`Rivers`), + Title: `Rivers`, + CRS: []ows.CRS{{Code: 4326, Namespace: `EPSG`}}, + Style: []*capabilities.Style{ + { + Name: `CenterLine`, + }, + }, + }, + { + Queryable: ip(1), + Name: sp(`Roads`), + Title: `Roads`, + CRS: []ows.CRS{{Code: 4326, Namespace: `EPSG`}}, + Style: []*capabilities.Style{ + { + Name: `CenterLine`, + }, + }, + }, + { + Queryable: ip(1), + Name: sp(`Houses`), + Title: `Houses`, + CRS: []ows.CRS{{Code: 4326, Namespace: `EPSG`}}, + Style: []*capabilities.Style{ + { + Name: `Outline`, + }, + }, + }, + }, + }, + }, + }, + } + + var gm = GetMap{ + BaseRequest: BaseRequest{ + Version: "1.3.0", + Attr: ows.XMLAttribute{ + xml.Attr{Name: xml.Name{Local: "xmlns"}, Value: "http://www.opengis.net/sld"}, + xml.Attr{Name: xml.Name{Space: "xmlns", Local: "gml"}, Value: "http://www.opengis.net/gml"}, + xml.Attr{Name: xml.Name{Space: "xmlns", Local: "ogc"}, Value: "http://www.opengis.net/ogc"}, + xml.Attr{Name: xml.Name{Space: "xmlns", Local: "ows"}, Value: "http://www.opengis.net/ows"}, + xml.Attr{Name: xml.Name{Space: "xmlns", Local: "se"}, Value: "http://www.opengis.net/se"}, + xml.Attr{Name: xml.Name{Space: "xmlns", Local: "wms"}, Value: "http://www.opengis.net/wms"}, + xml.Attr{Name: xml.Name{Space: "xmlns", Local: "xsi"}, Value: "http://www.w3.org/2001/XMLSchema-instance"}, + xml.Attr{Name: xml.Name{Space: "http://www.w3.org/2001/XMLSchema-instance", Local: "schemaLocation"}, Value: "http://www.opengis.net/sld GetMap.xsd"}, + }}, + StyledLayerDescriptor: StyledLayerDescriptor{ + Version: "1.1.0", + NamedLayer: []NamedLayer{ + {Name: "Rivers", NamedStyle: &NamedStyle{Name: "CenterLine"}}, + {Name: "Roads", NamedStyle: &NamedStyle{Name: "CenterLine"}}, + {Name: "Houses", NamedStyle: &NamedStyle{Name: "Outline"}}, + }}, + CRS: ows.CRS{Namespace: "EPSG", Code: 4326}, + BoundingBox: ows.BoundingBox{ + Crs: "http://www.opengis.net/gml/srs/epsg.xml#4326", + LowerCorner: [2]float64{-180.0, -90.0}, + UpperCorner: [2]float64{180.0, 90.0}, + }, + Output: Output{ + Size: Size{Width: 1024, Height: 512}, + Format: "image/jpeg", + Transparent: bp(false)}, + Exceptions: sp("XML"), + } + + for i := 0; i < b.N; i++ { + gm.Validate(capabilities) + } +} From 0fda0e2c1d7151658e67194b8cedc794b0cb0753 Mon Sep 17 00:00:00 2001 From: Wouter Date: Thu, 13 Aug 2020 19:36:37 +0200 Subject: [PATCH 43/45] layer crs exception --- pkg/wms130/exception/exception.go | 6 ++++++ pkg/wms130/request/getmap.go | 10 +++++----- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/pkg/wms130/exception/exception.go b/pkg/wms130/exception/exception.go index 51ce2ba..972888d 100644 --- a/pkg/wms130/exception/exception.go +++ b/pkg/wms130/exception/exception.go @@ -74,6 +74,12 @@ func InvalidCRS(s ...string) WMSException { ExceptionCode: `InvalidCRS`, } } + if len(s) == 2 { + return WMSException{ + ExceptionText: fmt.Sprintf("The CRS: %s is not known by the layer: %s", s[0], s[1]), + ExceptionCode: `InvalidCRS`, + } + } return WMSException{ ExceptionCode: `InvalidCRS`, } diff --git a/pkg/wms130/request/getmap.go b/pkg/wms130/request/getmap.go index 4863de5..d3efcf2 100644 --- a/pkg/wms130/request/getmap.go +++ b/pkg/wms130/request/getmap.go @@ -53,12 +53,12 @@ func (gm *GetMap) Validate(c ows.Capabilities) ows.Exceptions { exceptions = append(exceptions, gm.Output.Validate(wmsCapabilities)...) for _, sld := range gm.StyledLayerDescriptor.NamedLayer { - layer, exception := wmsCapabilities.GetLayer(sld.Name) - if exception != nil { - exceptions = append(exceptions, exception) + layer, layerexception := wmsCapabilities.GetLayer(sld.Name) + if layerexception != nil { + exceptions = append(exceptions, layerexception) } - if exception := checkCRS(gm.CRS, layer.CRS); exception != nil { - exceptions = append(exceptions, exception) + if CRSException := checkCRS(gm.CRS, layer.CRS); CRSException != nil { + exceptions = append(exceptions, exception.InvalidCRS(gm.CRS.String(), *layer.Name)) } } From 67d7ae88ee6d547bc1c64baa124fc8bcfd3f9d2c Mon Sep 17 00:00:00 2001 From: Wouter Date: Fri, 14 Aug 2020 09:24:32 +0200 Subject: [PATCH 44/45] added omitempty for xsi:type xml attr --- pkg/wms130/capabilities/capabilities.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/wms130/capabilities/capabilities.go b/pkg/wms130/capabilities/capabilities.go index 8fb8689..192c45e 100644 --- a/pkg/wms130/capabilities/capabilities.go +++ b/pkg/wms130/capabilities/capabilities.go @@ -235,7 +235,7 @@ type AuthorityURL struct { // ExtendedCapabilities containing the inspire extendedcapabilities, when available type ExtendedCapabilities struct { MetadataURL struct { - Type string `xml:"xsi:type,attr" yaml:"type"` + Type string `xml:"xsi:type,attr,omitempty" yaml:"type"` URL string `xml:"inspire_common:URL" yaml:"url"` MediaType string `xml:"inspire_common:MediaType" yaml:"mediatype"` } `xml:"inspire_common:MetadataUrl" yaml:"metadataurl"` From 10fdcbcc3895cd0aaf0b943aa918b2d4051dbc7d Mon Sep 17 00:00:00 2001 From: Wouter Date: Fri, 14 Aug 2020 09:53:16 +0200 Subject: [PATCH 45/45] added CRS parsestring tests --- pkg/ows/common.go | 9 ++++++++- pkg/ows/common_test.go | 40 ++++++++++++++++++++++++++++++---------- 2 files changed, 38 insertions(+), 11 deletions(-) diff --git a/pkg/ows/common.go b/pkg/ows/common.go index 6fee006..59391f5 100644 --- a/pkg/ows/common.go +++ b/pkg/ows/common.go @@ -8,8 +8,10 @@ import ( "strings" ) +// const ( codeSpace = `urn:ogc:def:crs:EPSG::` + EPSG = `EPSG` ) // BoundingBox struct @@ -114,7 +116,12 @@ func (c *CRS) parseString(s string) { regex := regexp.MustCompile(`(^.*):([0-9]+)`) code := regex.FindStringSubmatch(s) if len(code) == 3 { // code[0] is the full match, the other the parts - c.Namespace = code[1] + f := strings.Index(code[1], EPSG) + if f > -1 { + c.Namespace = EPSG + } else { + c.Namespace = code[1] + } // the regex already checks if it [0-9] i, _ := strconv.Atoi(code[2]) diff --git a/pkg/ows/common_test.go b/pkg/ows/common_test.go index d0b4c2e..1f47404 100644 --- a/pkg/ows/common_test.go +++ b/pkg/ows/common_test.go @@ -14,10 +14,10 @@ func TestBoundingBoxBuildQueryString(t *testing.T) { 0: {boundingbox: BoundingBox{}, boundingboxstring: `0.000000,0.000000,0.000000,0.000000`}, 1: {boundingbox: BoundingBox{LowerCorner: [2]float64{-180.0, -90.0}, UpperCorner: [2]float64{180.0, 90.0}}, boundingboxstring: `-180.000000,-90.000000,180.000000,90.000000`}, } - for k, a := range tests { - str := a.boundingbox.BuildKVP() - if str != a.boundingboxstring { - t.Errorf("test: %d, expected: %v+,\n got: %v+", k, a.boundingboxstring, str) + for k, test := range tests { + str := test.boundingbox.BuildKVP() + if str != test.boundingboxstring { + t.Errorf("test: %d, expected: %v+,\n got: %v+", k, test.boundingboxstring, str) } } } @@ -60,23 +60,43 @@ func TestStripDuplicateAttr(t *testing.T) { expected: []xml.Attr{{Name: xml.Name{Local: "gml"}, Value: "http://www.opengis.net/gml/3.2"}}}, } - for k, a := range tests { - stripped := StripDuplicateAttr(a.attributes) - if len(a.expected) != len(stripped) { - t.Errorf("test: %d, expected: %s,\n got: %s", k, a.expected, stripped) + for k, test := range tests { + stripped := StripDuplicateAttr(test.attributes) + if len(test.expected) != len(stripped) { + t.Errorf("test: %d, expected: %s,\n got: %s", k, test.expected, stripped) } else { c := false - for _, exceptedAttr := range a.expected { + for _, exceptedAttr := range test.expected { for _, result := range stripped { if exceptedAttr == result { c = true } } if !c { - t.Errorf("test: %d, expected: %s,\n got: %s", k, a.expected, stripped) + t.Errorf("test: %d, expected: %s,\n got: %s", k, test.expected, stripped) } c = false } } } } + +func TestCRSParseString(t *testing.T) { + var tests = []struct { + input string + expectedCRS CRS + }{ + 0: {}, // Empty input == empty struct + 1: {input: `urn:ogc:def:crs:EPSG::4326`, expectedCRS: CRS{Code: 4326, Namespace: `EPSG`}}, + 2: {input: `EPSG:4326`, expectedCRS: CRS{Code: 4326, Namespace: `EPSG`}}, + } + + for k, test := range tests { + var crs CRS + crs.parseString(test.input) + + if crs.Code != test.expectedCRS.Code || crs.Namespace != test.expectedCRS.Namespace { + t.Errorf("test: %d, expected: %v,\n got: %v", k, test.expectedCRS, crs) + } + } +}