Skip to content

Commit 647486a

Browse files
authored
Merge pull request #26 from PDOK/wr/validation
Wr/validation
2 parents 82961e6 + c39e60a commit 647486a

File tree

10 files changed

+315
-32
lines changed

10 files changed

+315
-32
lines changed

api/v1/ownerinfo_types.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,7 @@ type MetadataURL struct {
5555
type Atom struct {
5656
Author model.Author `json:"author"`
5757

58-
// +kubebuilder:validation:Pattern:=`https://.*|http://localhost.*`
59-
DefaultStylesheet *string `json:"defaultStylesheet,omitempty"`
58+
DefaultStylesheet *model.URL `json:"defaultStylesheet,omitempty"`
6059
}
6160

6261
// WFS contains Web Feature Service related information

config/crd/bases/pdok.nl_ownerinfo.yaml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,15 +47,19 @@ spec:
4747
or dataset
4848
properties:
4949
email:
50+
description: Email of the author
51+
format: email
5052
type: string
5153
name:
54+
description: Name of the author
55+
minLength: 1
5256
type: string
5357
required:
5458
- email
5559
- name
5660
type: object
5761
defaultStylesheet:
58-
pattern: https://.*
62+
pattern: ^https?://.+/.+
5963
type: string
6064
required:
6165
- author

go.mod

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,15 @@ require (
1111
github.com/onsi/ginkgo/v2 v2.22.1
1212
github.com/onsi/gomega v1.36.2
1313
github.com/pkg/errors v0.9.1
14+
github.com/stretchr/testify v1.10.0
15+
go.uber.org/zap v1.27.0
16+
gopkg.in/yaml.v3 v3.0.1
1417
k8s.io/api v0.32.3
1518
k8s.io/apiextensions-apiserver v0.32.3
1619
k8s.io/apimachinery v0.32.3
1720
k8s.io/apiserver v0.32.3
1821
k8s.io/client-go v0.32.3
22+
k8s.io/klog/v2 v2.130.1
1923
sigs.k8s.io/controller-runtime v0.20.4
2024
sigs.k8s.io/kustomize/api v0.19.0
2125
sigs.k8s.io/kustomize/kyaml v0.19.0
@@ -64,6 +68,7 @@ require (
6468
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
6569
github.com/modern-go/reflect2 v1.0.2 // indirect
6670
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
71+
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
6772
github.com/prometheus/client_golang v1.21.1 // indirect
6873
github.com/prometheus/client_model v0.6.1 // indirect
6974
github.com/prometheus/common v0.62.0 // indirect
@@ -82,7 +87,6 @@ require (
8287
go.opentelemetry.io/otel/trace v1.35.0 // indirect
8388
go.opentelemetry.io/proto/otlp v1.5.0 // indirect
8489
go.uber.org/multierr v1.11.0 // indirect
85-
go.uber.org/zap v1.27.0 // indirect
8690
golang.org/x/exp v0.0.0-20241215155358-4a5509556b9e // indirect
8791
golang.org/x/net v0.38.0 // indirect
8892
golang.org/x/oauth2 v0.28.0 // indirect
@@ -99,9 +103,7 @@ require (
99103
google.golang.org/protobuf v1.36.6 // indirect
100104
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
101105
gopkg.in/inf.v0 v0.9.1 // indirect
102-
gopkg.in/yaml.v3 v3.0.1 // indirect
103106
k8s.io/component-base v0.32.3 // indirect
104-
k8s.io/klog/v2 v2.130.1 // indirect
105107
k8s.io/kube-openapi v0.0.0-20241212222426-2c72e554b1e7 // indirect
106108
k8s.io/utils v0.0.0-20241210054802-24370beab758 // indirect
107109
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.1 // indirect

model/author.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@ package model
22

33
// Author represents the author or owner of the service or dataset
44
type Author struct {
5-
Name string `json:"name"`
5+
// Name of the author
6+
// +kubebuilder:validation:MinLength:=1
7+
Name string `json:"name"`
8+
9+
// Email of the author
10+
// +kubebuilder:validation:Format:=email
611
Email string `json:"email"`
712
}

model/bbox.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,16 @@ import (
1212
// BBox defines a bounding box with coordinates
1313
type BBox struct {
1414
// Linksboven X coördinaat
15-
// +kubebuilder:validation:Pattern="^[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)$"
15+
// +kubebuilder:validation:Pattern="^-?[0-9]+([.][0-9]*)?$"
1616
MinX string `json:"minx"`
1717
// Rechtsonder X coördinaat
18-
// +kubebuilder:validation:Pattern="^[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)$"
18+
// +kubebuilder:validation:Pattern="^-?[0-9]+([.][0-9]*)?$"
1919
MaxX string `json:"maxx"`
2020
// Linksboven Y coördinaat
21-
// +kubebuilder:validation:Pattern="^[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)$"
21+
// +kubebuilder:validation:Pattern="^-?[0-9]+([.][0-9]*)?$"
2222
MinY string `json:"miny"`
2323
// Rechtsonder Y coördinaat
24-
// +kubebuilder:validation:Pattern="^[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)$"
24+
// +kubebuilder:validation:Pattern="^-?[0-9]+([.][0-9]*)?$"
2525
MaxY string `json:"maxy"`
2626
}
2727

model/url.go

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package model
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"net/url"
7+
"regexp"
8+
"strings"
9+
10+
"gopkg.in/yaml.v3"
11+
)
12+
13+
var (
14+
validURLRegexp = regexp.MustCompile(`^https?://.+/.+$`)
15+
)
16+
17+
// URL Custom net.URL compatible with YAML and JSON (un)marshalling and kubebuilder.
18+
// In addition, it also removes trailing slash if present, so we can easily
19+
// append a longer path without having to worry about double slashes.
20+
//
21+
// Allow only http/https URLs or environment variables like ${FOOBAR}
22+
// +kubebuilder:validation:Pattern=`^https?://.+/.+`
23+
// +kubebuilder:validation:Type=string
24+
type URL struct {
25+
// This is a pointer so the wrapper can directly be used in templates, e.g.: {{ .Config.BaseURL }}
26+
// Otherwise you would need .String() or template.URL(). (Might be a bug.)
27+
*url.URL
28+
}
29+
30+
// UnmarshalYAML parses a string to URL and also removes trailing slash if present,
31+
// so we can easily append a longer path without having to worry about double slashes.
32+
func (u *URL) UnmarshalYAML(unmarshal func(any) error) error {
33+
var s string
34+
if err := unmarshal(&s); err != nil {
35+
return err
36+
}
37+
if parsedURL, err := ParseURL(s); err != nil {
38+
return err
39+
} else if parsedURL != nil {
40+
u.URL = parsedURL
41+
}
42+
return nil
43+
}
44+
45+
// MarshalJSON turns URL into JSON.
46+
// Value instead of pointer receiver because only that way it can be used for both.
47+
func (u URL) MarshalJSON() ([]byte, error) {
48+
if u.URL == nil {
49+
return json.Marshal("")
50+
}
51+
return json.Marshal(u.URL.String())
52+
}
53+
54+
// UnmarshalJSON parses a string to URL and also removes trailing slash if present,
55+
// so we can easily append a longer path without having to worry about double slashes.
56+
func (u *URL) UnmarshalJSON(b []byte) error {
57+
return yaml.Unmarshal(b, u)
58+
}
59+
60+
// MarshalYAML turns URL into YAML.
61+
// Value instead of pointer receiver because only that way it can be used for both.
62+
func (u URL) MarshalYAML() (interface{}, error) {
63+
if u.URL == nil {
64+
return "", nil
65+
}
66+
return u.URL.String(), nil
67+
}
68+
69+
// DeepCopyInto copies the receiver, writes into out.
70+
func (u *URL) DeepCopyInto(out *URL) {
71+
if out != nil {
72+
*out = *u
73+
}
74+
}
75+
76+
// DeepCopy copies the receiver, creates a new URL.
77+
func (u *URL) DeepCopy() *URL {
78+
if u == nil {
79+
return nil
80+
}
81+
out := &URL{}
82+
u.DeepCopyInto(out)
83+
return out
84+
}
85+
86+
func ParseURL(s string) (*url.URL, error) {
87+
if !validURLRegexp.MatchString(s) {
88+
return nil, fmt.Errorf("invalid URL: %s", s)
89+
}
90+
return url.Parse(strings.TrimSuffix(s, "/"))
91+
}

model/url_test.go

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
package model
2+
3+
import (
4+
"encoding/json"
5+
"errors"
6+
"net/url"
7+
"testing"
8+
9+
"github.com/stretchr/testify/assert"
10+
"gopkg.in/yaml.v3"
11+
)
12+
13+
type TestEmbeddedURL struct {
14+
U URL `json:"u" yaml:"u"`
15+
}
16+
17+
func TestURL_DeepCopy(t *testing.T) {
18+
tests := []struct {
19+
url *URL
20+
}{
21+
{
22+
url: &URL{URL: &url.URL{Scheme: "https", Host: "tiles.foobar.example", Path: "/somedataset/"}},
23+
},
24+
}
25+
for _, tt := range tests {
26+
t.Run("", func(t *testing.T) {
27+
got := tt.url.DeepCopy()
28+
assert.Equal(t, tt.url, got, "DeepCopy")
29+
assert.NotSamef(t, tt.url, got, "DeepCopy")
30+
})
31+
}
32+
}
33+
34+
func TestURL_DeepCopyInto(t *testing.T) {
35+
tests := []struct {
36+
url *URL
37+
}{
38+
{
39+
url: &URL{URL: &url.URL{Scheme: "https", Host: "tiles.foobar.example", Path: "/somedataset/"}},
40+
},
41+
}
42+
for _, tt := range tests {
43+
t.Run("", func(t *testing.T) {
44+
got := &URL{}
45+
tt.url.DeepCopyInto(got)
46+
assert.Equal(t, tt.url, got, "DeepCopyInto")
47+
assert.NotSamef(t, tt.url, got, "DeepCopyInto")
48+
})
49+
}
50+
}
51+
52+
func TestURL_Marshalling_JSON(t *testing.T) {
53+
tests := []struct {
54+
url *URL
55+
want string
56+
wantErr assert.ErrorAssertionFunc
57+
}{
58+
{
59+
url: &URL{URL: &url.URL{Scheme: "https", Host: "tiles.foobar.example", Path: "/somedataset/"}},
60+
want: `"https://tiles.foobar.example/somedataset/"`,
61+
wantErr: assert.NoError,
62+
},
63+
{
64+
url: &URL{},
65+
want: `""`,
66+
wantErr: assert.NoError,
67+
},
68+
}
69+
for _, tt := range tests {
70+
t.Run("", func(t *testing.T) {
71+
marshalled, err := json.Marshal(tt.url)
72+
if !tt.wantErr(t, err, errors.New("json.Marshal")) {
73+
return
74+
}
75+
assert.Equalf(t, tt.want, string(marshalled), "json.Marshal")
76+
77+
// non-pointer
78+
marshalled, err = json.Marshal(*tt.url)
79+
if !tt.wantErr(t, err, errors.New("json.Marshal")) {
80+
return
81+
}
82+
assert.Equalf(t, tt.want, string(marshalled), "json.Marshal")
83+
})
84+
}
85+
}
86+
87+
func TestURL_Unmarshalling_JSON(t *testing.T) {
88+
tests := []struct {
89+
url string
90+
want *URL
91+
wantErr assert.ErrorAssertionFunc
92+
}{
93+
{
94+
url: `"https://tiles.foobar.example/somedataset/"`,
95+
want: &URL{URL: &url.URL{Scheme: "https", Host: "tiles.foobar.example", Path: "/somedataset"}}, // no trailing slash
96+
wantErr: assert.NoError,
97+
},
98+
}
99+
for _, tt := range tests {
100+
t.Run("", func(t *testing.T) {
101+
unmarshalled := &URL{}
102+
err := json.Unmarshal([]byte(tt.url), unmarshalled)
103+
if !tt.wantErr(t, err, errors.New("json.Unmarshal")) {
104+
return
105+
}
106+
assert.Equalf(t, tt.want, unmarshalled, "json.Unmarshal")
107+
108+
// non-pointer
109+
unmarshalledEmbedded := &TestEmbeddedURL{}
110+
err = json.Unmarshal([]byte(`{"U": `+tt.url+`}`), unmarshalledEmbedded)
111+
if !tt.wantErr(t, err, errors.New("json.Unmarshal")) {
112+
return
113+
}
114+
assert.EqualValuesf(t, &TestEmbeddedURL{U: *tt.want}, unmarshalledEmbedded, "json.Unmarshal")
115+
})
116+
}
117+
}
118+
119+
func TestURL_Marshalling_YAML(t *testing.T) {
120+
tests := []struct {
121+
url *URL
122+
want string
123+
wantErr assert.ErrorAssertionFunc
124+
}{
125+
{
126+
url: &URL{URL: &url.URL{Scheme: "https", Host: "tiles.foobar.example", Path: "/somedataset/"}},
127+
want: `https://tiles.foobar.example/somedataset/` + "\n",
128+
wantErr: assert.NoError,
129+
},
130+
{
131+
url: &URL{},
132+
want: `""` + "\n",
133+
wantErr: assert.NoError,
134+
},
135+
}
136+
for _, tt := range tests {
137+
t.Run("", func(t *testing.T) {
138+
marshalled, err := yaml.Marshal(tt.url)
139+
if !tt.wantErr(t, err, errors.New("yaml.Marshal")) {
140+
return
141+
}
142+
assert.Equalf(t, tt.want, string(marshalled), "yaml.Marshal")
143+
144+
// non-pointer
145+
marshalled, err = yaml.Marshal(*tt.url)
146+
if !tt.wantErr(t, err, errors.New("yaml.Marshal")) {
147+
return
148+
}
149+
assert.Equalf(t, tt.want, string(marshalled), "yaml.Marshal")
150+
})
151+
}
152+
}
153+
154+
func TestURL_Unmarshalling_YAML(t *testing.T) {
155+
tests := []struct {
156+
url string
157+
want *URL
158+
wantErr assert.ErrorAssertionFunc
159+
}{
160+
{
161+
url: `https://tiles.foobar.example/somedataset/` + "\n",
162+
want: &URL{URL: &url.URL{Scheme: "https", Host: "tiles.foobar.example", Path: "/somedataset"}}, // no trailing slash
163+
wantErr: assert.NoError,
164+
},
165+
}
166+
for _, tt := range tests {
167+
t.Run("", func(t *testing.T) {
168+
unmarshalled := &URL{}
169+
err := yaml.Unmarshal([]byte(tt.url), unmarshalled)
170+
if !tt.wantErr(t, err, errors.New("yaml.Unmarshal")) {
171+
return
172+
}
173+
assert.Equalf(t, tt.want, unmarshalled, "yaml.Unmarshal")
174+
175+
// non-pointer
176+
unmarshalledEmbedded := &TestEmbeddedURL{}
177+
err = yaml.Unmarshal([]byte(`{"u": `+tt.url+`}`), unmarshalledEmbedded)
178+
if !tt.wantErr(t, err, errors.New("yaml.Unmarshal")) {
179+
return
180+
}
181+
assert.EqualValuesf(t, &TestEmbeddedURL{U: *tt.want}, unmarshalledEmbedded, "yaml.Unmarshal")
182+
})
183+
}
184+
}

0 commit comments

Comments
 (0)