-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathvalidate_values.go
More file actions
139 lines (135 loc) · 3.29 KB
/
Copy pathvalidate_values.go
File metadata and controls
139 lines (135 loc) · 3.29 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
package codemeta
import (
"net/url"
"strings"
"time"
"unicode"
)
func isAgentRange(expected string) bool {
for _, name := range strings.Fields(expected) {
if name == rangePerson || name == rangeOrganization {
return true
}
}
return false
}
func matchesExpected(v Value, expected string) bool {
for _, typ := range strings.Fields(expected) {
switch typ {
case rangeText, rangeURL, rangeDate, rangeDatetime:
if v.kind == String {
return true
}
case rangeNumber:
if v.kind == Number {
return true
}
case rangeInteger:
if v.kind == Number && integerNumber(v.text) {
return true
}
case rangeBoolean:
if v.kind == Boolean {
return true
}
default:
if v.kind == Object {
return true
}
}
}
return false
}
func integerNumber(text string) bool {
// JSON integers written with a decimal or exponent are evaluated without float conversion.
mantissa, exponent, hasExponent := strings.Cut(strings.ToLower(text), "e")
scale := 0
if hasExponent {
sign := 1
if strings.HasPrefix(exponent, "-") {
sign = -1
}
exponent = strings.TrimLeft(exponent, "+-0")
for _, r := range exponent {
if scale > len(text) {
break
}
scale = scale*decimalBase + int(r-'0')
}
scale *= sign
}
whole, fraction, _ := strings.Cut(mantissa, ".")
digits := strings.TrimLeft(strings.TrimPrefix(whole, "-")+fraction, "0")
if digits == "" {
return true
}
required := len(fraction) - scale
if required <= 0 {
return true
}
if required > len(digits) {
return false
}
return strings.Trim(digits[len(digits)-required:], "0") == ""
}
func validURL(text string) bool {
if text == "" || strings.ContainsFunc(text, func(r rune) bool { return unicode.IsSpace(r) || unicode.IsControl(r) }) {
return false
}
u, err := url.Parse(text)
if err != nil || u.Scheme == "" {
return false
}
if u.Scheme == "http" || u.Scheme == "https" {
return u.Host != ""
}
return u.Opaque != "" || u.Host != "" || u.Path != ""
}
func validDate(text string, datetime bool) bool {
for _, layout := range []string{"2006", "2006-01", "2006-01-02"} {
if _, err := time.Parse(layout, text); err == nil {
return true
}
}
if datetime {
_, err := time.Parse(time.RFC3339, text)
return err == nil
}
return false
}
func (c *validator) stringValue(v Value, def termDefinition, path string) {
types := strings.Fields(def.expected)
text, date, datetime, urlType := false, false, false, false
for _, typ := range types {
switch typ {
case rangeText:
text = true
case rangeDate:
date = true
case rangeDatetime:
datetime = true
case rangeURL:
urlType = true
}
}
if normalizeIRI(def.coercion) == schemaDate {
date = true
}
if !text && (date || datetime) && !validDate(v.text, datetime) {
c.add(v.pos, path, "invalid_date", "invalid calendar date or date-time")
}
if !text && urlType && !validURL(v.text) {
c.add(v.pos, path, "invalid_url", "expected an absolute URL")
}
if !text && !urlType && def.coercion == keywordID && !validIRI(v.text) {
c.add(v.pos, path, "invalid_iri", "expected an IRI reference")
}
}
func validIRI(text string) bool {
if strings.ContainsFunc(text, func(r rune) bool { return unicode.IsSpace(r) || unicode.IsControl(r) }) {
return false
}
_, err := url.Parse(text)
return err == nil
}
const schemaDate = "https://schema.org/Date"