Test Failed
Pull Request — main (#75)
by Igor
01:57
created

validation.ArrayIndex.String   A

Complexity

Conditions 1

Size

Total Lines 2
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 2
dl 0
loc 2
rs 10
c 0
b 0
f 0
nop 0
ccs 1
cts 1
cp 1
crap 1
1
package validation
2
3
import (
4
	"encoding/json"
5
	"fmt"
6
	"strconv"
7
	"strings"
8
	"unicode"
9
)
10
11
// PropertyPathElement is a part of the PropertyPath.
12
type PropertyPathElement interface {
13
	// IsIndex can be used to determine whether an element is a string (property name) or
14
	// an index array.
15
	IsIndex() bool
16
	fmt.Stringer
17
}
18
19
// PropertyName holds up property name value under PropertyPath.
20
type PropertyName string
21
22 1
// IsIndex on PropertyName always returns false.
23
func (p PropertyName) IsIndex() bool {
24
	return false
25
}
26
27 1
// String returns property name as is.
28
func (p PropertyName) String() string {
29
	return string(p)
30
}
31
32
// ArrayIndex holds up array index value under PropertyPath.
33
type ArrayIndex int
34
35 1
// IsIndex on ArrayIndex always returns true.
36
func (a ArrayIndex) IsIndex() bool {
37
	return true
38
}
39
40 1
// String returns array index values converted into a string.
41
func (a ArrayIndex) String() string {
42
	return strconv.Itoa(int(a))
43
}
44
45
// PropertyPath is generated by the validator and indicates how it reached the invalid value
46
// from the root element. Property path is denoted by dots, while array access
47
// is denoted by square brackets. For example, "book.keywords[0]" means that the violation
48
// occurred on the first element of array "keywords" in the "book" object.
49
//
50
// Internally PropertyPath is a linked list. You can create a new path using WithProperty
51
// or WithIndex methods. PropertyPath should always be used as a pointer value.
52
// Nil value is a valid value that means that the property path is empty.
53
type PropertyPath struct {
54
	parent *PropertyPath
55
	value  PropertyPathElement
56
}
57
58
// NewPropertyPath creates a PropertyPath from the list of elements. If the list is empty nil will be returned.
59 1
// Nil value is a valid value that means that the property path is empty.
60 1
func NewPropertyPath(elements ...PropertyPathElement) *PropertyPath {
61 1
	var path *PropertyPath
62
63 1
	return path.With(elements...)
64
}
65
66
// With returns new PropertyPath with appended elements to the end of the list.
67
func (path *PropertyPath) With(elements ...PropertyPathElement) *PropertyPath {
68 1
	current := path
69
	for _, element := range elements {
70
		current = &PropertyPath{parent: current, value: element}
71
	}
72
	return current
73
}
74
75
// WithProperty returns new PropertyPath with appended PropertyName to the end of the list.
76 1
func (path *PropertyPath) WithProperty(name string) *PropertyPath {
77
	return &PropertyPath{
78
		parent: path,
79
		value:  PropertyName(name),
80
	}
81
}
82
83
// WithIndex returns new PropertyPath with appended ArrayIndex to the end of the list.
84 1
func (path *PropertyPath) WithIndex(index int) *PropertyPath {
85 1
	return &PropertyPath{
86 1
		parent: path,
87 1
		value:  ArrayIndex(index),
88 1
	}
89
}
90 1
91 1
// String is used to format property path to a string.
92 1
func (path *PropertyPath) String() string {
93
	s := ""
94
	element := path
95 1
	for element != nil {
96
		if element.value.IsIndex() {
97
			s = "[" + element.value.String() + "]" + s
98 1
		} else {
99
			name, isBracketed := encodePropertyName(element.value.String())
100
			s = name + s
101
			if !isBracketed && element.parent != nil {
102
				s = "." + s
103 1
			}
104
		}
105
		element = element.parent
106
	}
107
108
	return s
109
}
110
111
// MarshalJSON will marshal property path value to a JSON string.
112
func (path *PropertyPath) MarshalJSON() ([]byte, error) {
113
	return json.Marshal(path.String())
114
}
115
116
func encodePropertyName(name string) (string, bool) {
117
	chars := []rune(name)
118
	if len(chars) == 0 {
119
		return `['']`, false
120
	}
121
122
	var encoded strings.Builder
123
	encoded.Grow(len(chars))
124
125
	needBracketing := false
126
	if !unicode.IsLetter(chars[0]) {
127
		needBracketing = true
128
	}
129
	for i := 0; i < len(chars); i++ {
130
		c := chars[i]
131
		if i > 0 && !unicode.IsLetter(c) && !unicode.IsDigit(c) {
132
			needBracketing = true
133
		}
134
		if c == '\'' || c == '\\' {
135
			encoded.WriteRune('\\')
136
		}
137
		encoded.WriteRune(c)
138
	}
139
140
	s := encoded.String()
141
	if needBracketing {
142
		return `['` + s + `']`, true
143
	}
144
145
	return s, false
146
}
147