Skip to content

Commit 6205399

Browse files
author
Thomas Osterbind
committed
feat(jsonschema format): added jsonpointer, reljsonpointer validators
1 parent 6fbfa05 commit 6205399

File tree

2 files changed

+73
-12
lines changed

2 files changed

+73
-12
lines changed

keywords_format.go

Lines changed: 70 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,32 @@
11
package jsonschema
22

33
import (
4+
// "encoding/json"
45
"fmt"
56
"net"
7+
"net/url"
68
"regexp"
9+
"strconv"
710
"strings"
811
"time"
912
)
1013

1114
const (
12-
email string = "^(((([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+(\\.([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+)*)|((\\x22)((((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(([\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(\\([\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}]))))*(((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(\\x22)))@((([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])([a-zA-Z]|\\d|-|\\.|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.)+(([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])([a-zA-Z]|\\d|-|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.?$"
13-
hostname string = `^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])(\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9]))*$`
15+
email string = "^(((([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+(\\.([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+)*)|((\\x22)((((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(([\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(\\([\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}]))))*(((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(\\x22)))@((([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])([a-zA-Z]|\\d|-|\\.|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.)+(([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])([a-zA-Z]|\\d|-|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.?$"
16+
hostname string = `^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])(\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9]))*$`
17+
unescapedTilda = `\~[^01]`
18+
endingTilda = `\~$`
1419
)
1520

1621
var (
17-
emailPattern = regexp.MustCompile(email)
18-
hostnamePattern = regexp.MustCompile(hostname)
22+
emailPattern = regexp.MustCompile(email)
23+
hostnamePattern = regexp.MustCompile(hostname)
24+
unescaptedTildaPattern = regexp.MustCompile(unescapedTilda)
25+
endingTildaPattern = regexp.MustCompile(endingTilda)
1926
)
2027

28+
// for json pointers
29+
2130
// func FormatType(data interface{}) string {
2231
// switch
2332
// }
@@ -68,6 +77,10 @@ func (f format) Validate(data interface{}) error {
6877
return isValidURITemplate(str)
6978
case "uri":
7079
return isValidURI(str)
80+
default:
81+
// TODO: should we return an error saying that we don't know that
82+
// format? or should we keep it as is (ignore, return nil)
83+
return nil
7184
}
7285
}
7386
return nil
@@ -85,8 +98,8 @@ func isValidDateTime(dateTime string) error {
8598
}
8699

87100
// A string instance is valid against "date" if it is a valid
88-
// representation according to the "date" production derived from RFC
89-
// 3339, section 5.6 [RFC3339]
101+
// representation according to the "full-date" production derived
102+
// from RFC 3339, section 5.6 [RFC3339]
90103
// https://tools.ietf.org/html/rfc3339#section-5.6
91104
func isValidDate(date string) error {
92105
arbitraryTime := "T08:30:06.283185Z"
@@ -180,23 +193,68 @@ func isValidIri(iri string) error {
180193
// RFC 6901, section 5 [RFC6901].
181194
// https://tools.ietf.org/html/rfc6901#section-5
182195
func isValidJSONPointer(jsonPointer string) error {
196+
// if !validateEscapeChars(jsonPointer) {
197+
// return fmt.Errorf("json pointer includes unescaped characters")
198+
// }
199+
// if _, err := jsonpointer.Parse(jsonPointer); err != nil {
200+
// return fmt.Errorf("invalid json pointer: %s", err.Error())
201+
// }
202+
if len(jsonPointer) == 0 {
203+
return nil
204+
}
205+
if jsonPointer[0] != '/' {
206+
return fmt.Errorf("non-empty references must begin with a '/' character")
207+
}
208+
str := jsonPointer[1:]
209+
if unescaptedTildaPattern.MatchString(str) {
210+
return fmt.Errorf("unescaped tilda error")
211+
}
212+
if endingTildaPattern.MatchString(str) {
213+
return fmt.Errorf("unescaped tilda error")
214+
}
183215
return nil
184216
}
185217

186218
// A string instance is a valid against "regex" if it is a valid
219+
// regular expression according to the ECMA 262 [ecma262] regular
220+
// expression dialect. Implementations that validate formats MUST
221+
// accept at least the subset of ECMA 262 defined in the Regular
222+
// Expressions [regexInterop] section of this specification, and
223+
// SHOULD accept all valid ECMA 262 expressions.
224+
// http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf
225+
// http://json-schema.org/latest/json-schema-validation.html#regexInterop
226+
// https://tools.ietf.org/html/rfc7159
187227
func isValidRegex(regex string) error {
188228
return nil
189229
}
190230

191231
// A string instance is a valid against "relative-json-pointer" if it
192232
// is a valid Relative JSON Pointer [relative-json-pointer].
193233
// https://tools.ietf.org/html/draft-handrews-relative-json-pointer-00
194-
func isValidRelJSONPointer(relJsonPointer string) error {
195-
return nil
234+
func isValidRelJSONPointer(relJSONPointer string) error {
235+
parts := strings.Split(relJSONPointer, "/")
236+
if len(parts) == 1 {
237+
parts = strings.Split(relJSONPointer, "#")
238+
}
239+
if i, err := strconv.Atoi(parts[0]); err != nil || i < 0 {
240+
return fmt.Errorf("RJP must begin with positive integer")
241+
}
242+
//skip over first part
243+
str := relJSONPointer[len(parts[0]):]
244+
if len(str) > 0 && str[0] == '#' {
245+
return nil
246+
}
247+
return isValidJSONPointer(str)
196248
}
197249

198-
// A string instance is a valid against "time" if it is a valid
250+
// A string instance is valid against "time" if it is a valid
251+
// representation according to the "full-time" production derived
252+
// from RFC 3339, section 5.6 [RFC3339]
253+
// https://tools.ietf.org/html/rfc3339#section-5.6
199254
func isValidTime(time string) error {
255+
arbitraryDate := "1963-06-19"
256+
dateTime := fmt.Sprintf("%sT%s", arbitraryDate, time)
257+
return isValidDateTime(dateTime)
200258
return nil
201259
}
202260

@@ -221,5 +279,8 @@ func isValidURITemplate(uriTemplate string) error {
221279
// according to [RFC3986].
222280
// https://tools.ietf.org/html/rfc3986
223281
func isValidURI(uri string) error {
282+
if _, err := url.Parse(uri); err != nil {
283+
return fmt.Errorf("uri incorrectly formatted: %s", err.Error())
284+
}
224285
return nil
225286
}

schema_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -287,13 +287,13 @@ func TestDraft7(t *testing.T) {
287287
"testdata/draft7/optional/format/hostname.json",
288288
"testdata/draft7/optional/format/ipv4.json",
289289
// "testdata/draft7/optional/format/iri.json",
290-
// "testdata/draft7/optional/format/relative-json-pointer.json",
290+
"testdata/draft7/optional/format/relative-json-pointer.json",
291291
// "testdata/draft7/optional/format/uri-template.json",
292292
"testdata/draft7/optional/format/date.json",
293293
// "testdata/draft7/optional/format/idn-email.json",
294294
"testdata/draft7/optional/format/ipv6.json",
295-
// "testdata/draft7/optional/format/json-pointer.json",
296-
// "testdata/draft7/optional/format/time.json",
295+
"testdata/draft7/optional/format/json-pointer.json",
296+
"testdata/draft7/optional/format/time.json",
297297
// "testdata/draft7/optional/format/uri.json",
298298
"testdata/draft7/optional/format/email.json",
299299
// "testdata/draft7/optional/format/idn-hostname.json",

0 commit comments

Comments
 (0)