| 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 |  |  |  |