github.com/eris-ltd/erisdb@v0.25.0/deploy/def/rule/rules.go (about) 1 package rule 2 3 import ( 4 "errors" 5 "fmt" 6 "regexp" 7 "strconv" 8 9 "strings" 10 11 "reflect" 12 13 validation "github.com/go-ozzo/ozzo-validation" 14 "github.com/hyperledger/burrow/acm" 15 "github.com/hyperledger/burrow/crypto" 16 "github.com/hyperledger/burrow/permission" 17 ) 18 19 var PlaceholderRegex = regexp.MustCompile(`\$(?P<bracket>{?)(?P<job>[[:word:]]+)(\.(?P<variable>[[:word:]]+))?}?`) 20 21 func MatchPlaceholders(str string) []PlaceholderMatch { 22 matches := PlaceholderRegex.FindAllStringSubmatch(str, -1) 23 pms := make([]PlaceholderMatch, 0, len(matches)) 24 for _, match := range matches { 25 pm := NewPlaceholderMatch(match) 26 if pm.IsMatch() { 27 pms = append(pms, pm) 28 } 29 } 30 return pms 31 } 32 33 type PlaceholderMatch struct { 34 Match string 35 JobName string 36 VariableName string 37 } 38 39 func (pm PlaceholderMatch) String() string { 40 var varStr string 41 if pm.VariableName != "" { 42 varStr = fmt.Sprintf(", Variable: %s", pm.VariableName) 43 } 44 return fmt.Sprintf("PlaceholderMatch{'%s': Job: %s%s}", pm.Match, pm.JobName, varStr) 45 } 46 47 func (pm PlaceholderMatch) IsMatch() bool { 48 return pm.Match != "" 49 } 50 51 func NewPlaceholderMatch(match []string) (pm PlaceholderMatch) { 52 pm.Match = match[0] 53 for i, name := range PlaceholderRegex.SubexpNames() { 54 switch name { 55 case "bracket": 56 if match[i] == "{" { 57 stripMatch := PlaceholderRegex.FindStringSubmatch(stripBraces(pm.Match)) 58 if len(stripMatch) == 0 { 59 return PlaceholderMatch{} 60 } 61 // Match stripped but keep the outer match with brackets for use as replacement string 62 pmStripped := NewPlaceholderMatch(stripMatch) 63 pm.JobName = pmStripped.JobName 64 pm.VariableName = pmStripped.VariableName 65 } 66 case "job": 67 pm.JobName = match[i] 68 case "variable": 69 pm.VariableName = match[i] 70 } 71 } 72 return 73 } 74 75 // Strips braces and return simple variable confined between braces 76 func stripBraces(str string) string { 77 bs := []byte(str) 78 const lb = byte('{') 79 const rb = byte('}') 80 start := 0 81 for i := 0; i < len(bs); i++ { 82 switch bs[i] { 83 case lb: 84 start = i + 1 85 case rb: 86 return `\$` + str[start:i] 87 } 88 } 89 return str[start:] 90 } 91 92 var exampleAddress = acm.GeneratePrivateAccountFromSecret("marmot").GetAddress() 93 94 // Rules 95 var ( 96 Placeholder = validation.Match(PlaceholderRegex).Error("must be a variable placeholder like $marmotVariable") 97 98 Address = validation.NewStringRule(IsAddress, 99 fmt.Sprintf("must be valid 20 byte hex-encoded string like '%v'", exampleAddress)) 100 101 AddressOrPlaceholder = Or(Placeholder, Address) 102 103 Relation = validation.In("eq", "ne", "ge", "gt", "le", "lt", "==", "!=", ">=", ">", "<=", "<") 104 105 PermissionOrPlaceholder = Or(Placeholder, Permission) 106 107 Permission = validation.By(func(value interface{}) error { 108 str, err := validation.EnsureString(value) 109 if err != nil { 110 return fmt.Errorf("must be a permission name, but %v is not a string", value) 111 } 112 _, err = permission.PermStringToFlag(str) 113 if err != nil { 114 return err 115 } 116 return nil 117 }) 118 119 Uint64OrPlaceholder = Or(Placeholder, Uint64) 120 121 Uint64 = validation.By(func(value interface{}) error { 122 str, err := validation.EnsureString(value) 123 if err != nil { 124 return fmt.Errorf("should be a numeric string but '%v' is not a string", value) 125 } 126 _, err = strconv.ParseUint(str, 10, 64) 127 if err != nil { 128 return fmt.Errorf("should be a 64 bit unsigned integer: ") 129 } 130 return nil 131 }) 132 ) 133 134 func Exactly(identity interface{}) validation.Rule { 135 return validation.By(func(value interface{}) error { 136 if !reflect.DeepEqual(identity, value) { 137 return fmt.Errorf("value %v does not exactly match %v", value, identity) 138 } 139 return nil 140 }) 141 } 142 143 func Or(rules ...validation.Rule) *orRule { 144 return &orRule{ 145 rules: rules, 146 } 147 } 148 149 type orRule struct { 150 rules []validation.Rule 151 } 152 153 func (orr *orRule) Validate(value interface{}) error { 154 errs := make([]string, len(orr.rules)) 155 for i, r := range orr.rules { 156 err := r.Validate(value) 157 if err == nil { 158 return nil 159 } 160 errs[i] = err.Error() 161 } 162 return fmt.Errorf("did not validate any requirements: %s", strings.Join(errs, ", ")) 163 } 164 165 func IsAddress(value string) bool { 166 _, err := crypto.AddressFromHexString(value) 167 return err == nil 168 } 169 170 // Returns true IFF value is zero value or has length 0 171 func IsOmitted(value interface{}) bool { 172 value, isNil := validation.Indirect(value) 173 if isNil || validation.IsEmpty(value) { 174 return true 175 } 176 // Accept and empty slice or map 177 length, err := validation.LengthOfValue(value) 178 if err == nil && length == 0 { 179 return true 180 } 181 return false 182 } 183 184 type boolRule struct { 185 isValid func(value interface{}) bool 186 message string 187 } 188 189 func New(isValid func(value interface{}) bool, message string, args ...interface{}) *boolRule { 190 return &boolRule{ 191 isValid: isValid, 192 message: fmt.Sprintf(message, args...), 193 } 194 } 195 196 func (r *boolRule) Validate(value interface{}) error { 197 if r.isValid(value) { 198 return nil 199 } 200 return errors.New(r.message) 201 } 202 203 func (r *boolRule) Error(message string, args ...interface{}) *boolRule { 204 r.message = fmt.Sprintf(message, args...) 205 return r 206 }