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

validation.*PropertyPath.MarshalJSON   A

Complexity

Conditions 1

Size

Total Lines 2
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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