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  }