github.com/furusax0621/goa-v1@v1.4.3/design/example.go (about)

     1  package design
     2  
     3  import (
     4  	"fmt"
     5  	"math"
     6  	"regexp"
     7  	"time"
     8  
     9  	regen "github.com/zach-klippenstein/goregen"
    10  )
    11  
    12  // exampleGenerator generates a random example based on the given validations on the definition.
    13  type exampleGenerator struct {
    14  	a *AttributeDefinition
    15  	r *RandomGenerator
    16  }
    17  
    18  // newExampleGenerator returns an example generator that uses the given random generator.
    19  func newExampleGenerator(a *AttributeDefinition, r *RandomGenerator) *exampleGenerator {
    20  	return &exampleGenerator{a, r}
    21  }
    22  
    23  // Maximum number of tries for generating example.
    24  const maxAttempts = 500
    25  
    26  // Generate generates a random value based on the given validations.
    27  func (eg *exampleGenerator) Generate(seen []string) interface{} {
    28  	// Randomize array length first, since that's from higher level
    29  	if eg.hasLengthValidation() {
    30  		return eg.generateValidatedLengthExample(seen)
    31  	}
    32  	// Enum should dominate, because the potential "examples" are fixed
    33  	if eg.hasEnumValidation() {
    34  		return eg.generateValidatedEnumExample()
    35  	}
    36  	// loop until a satisified example is generated
    37  	hasFormat, hasPattern, hasMinMax := eg.hasFormatValidation(), eg.hasPatternValidation(), eg.hasMinMaxValidation()
    38  	attempts := 0
    39  	for attempts < maxAttempts {
    40  		attempts++
    41  		var example interface{}
    42  		// Format comes first, since it initiates the example
    43  		if hasFormat {
    44  			example = eg.generateFormatExample()
    45  		}
    46  		// now validate with the rest of matchers; if not satisified, redo
    47  		if hasPattern {
    48  			if example == nil {
    49  				example = eg.generateValidatedPatternExample()
    50  			} else if !eg.checkPatternValidation(example) {
    51  				continue
    52  			}
    53  		}
    54  		if hasMinMax {
    55  			if example == nil {
    56  				example = eg.generateValidatedMinMaxValueExample()
    57  			} else if !eg.checkMinMaxValueValidation(example) {
    58  				continue
    59  			}
    60  		}
    61  		if example == nil {
    62  			example = eg.a.Type.GenerateExample(eg.r, seen)
    63  		}
    64  		return example
    65  	}
    66  	return eg.a.Type.GenerateExample(eg.r, seen)
    67  }
    68  
    69  func (eg *exampleGenerator) ExampleLength() int {
    70  	if eg.hasLengthValidation() {
    71  		minlength, maxlength := math.Inf(1), math.Inf(-1)
    72  		if eg.a.Validation.MinLength != nil {
    73  			minlength = float64(*eg.a.Validation.MinLength)
    74  		}
    75  		if eg.a.Validation.MaxLength != nil {
    76  			maxlength = float64(*eg.a.Validation.MaxLength)
    77  		}
    78  		count := 0
    79  		if math.IsInf(minlength, 1) {
    80  			count = int(maxlength) - (eg.r.Int() % 3)
    81  		} else if math.IsInf(maxlength, -1) {
    82  			count = int(minlength) + (eg.r.Int() % 3)
    83  		} else if minlength < maxlength {
    84  			diff := int(maxlength - minlength)
    85  			if diff > maxExampleLength {
    86  				diff = maxExampleLength
    87  			}
    88  			count = int(minlength) + (eg.r.Int() % diff)
    89  		} else if minlength == maxlength {
    90  			count = int(minlength)
    91  		} else {
    92  			panic("Validation: MinLength > MaxLength")
    93  		}
    94  		if count > maxExampleLength {
    95  			count = maxExampleLength
    96  		}
    97  		if count <= 0 && maxlength != 0 {
    98  			count = 1
    99  		}
   100  		return count
   101  	}
   102  	return eg.r.Int()%3 + 1
   103  }
   104  
   105  func (eg *exampleGenerator) hasLengthValidation() bool {
   106  	if eg.a.Validation == nil {
   107  		return false
   108  	}
   109  	return eg.a.Validation.MinLength != nil || eg.a.Validation.MaxLength != nil
   110  }
   111  
   112  const maxExampleLength = 10
   113  
   114  // generateValidatedLengthExample generates a random size array of examples based on what's given.
   115  func (eg *exampleGenerator) generateValidatedLengthExample(seen []string) interface{} {
   116  	count := eg.ExampleLength()
   117  	if !eg.a.Type.IsArray() {
   118  		return eg.r.faker.Characters(count)
   119  	}
   120  	res := make([]interface{}, count)
   121  	for i := 0; i < count; i++ {
   122  		res[i] = eg.a.Type.ToArray().ElemType.GenerateExample(eg.r, seen)
   123  	}
   124  	return res
   125  }
   126  
   127  func (eg *exampleGenerator) hasEnumValidation() bool {
   128  	return eg.a.Validation != nil && len(eg.a.Validation.Values) > 0
   129  }
   130  
   131  // generateValidatedEnumExample returns a random selected enum value.
   132  func (eg *exampleGenerator) generateValidatedEnumExample() interface{} {
   133  	if !eg.hasEnumValidation() {
   134  		return nil
   135  	}
   136  	values := eg.a.Validation.Values
   137  	count := len(values)
   138  	i := eg.r.Int() % count
   139  	return values[i]
   140  }
   141  
   142  func (eg *exampleGenerator) hasFormatValidation() bool {
   143  	return eg.a.Validation != nil && eg.a.Validation.Format != ""
   144  }
   145  
   146  // generateFormatExample returns a random example based on the format the user asks.
   147  func (eg *exampleGenerator) generateFormatExample() interface{} {
   148  	if !eg.hasFormatValidation() {
   149  		return nil
   150  	}
   151  	format := eg.a.Validation.Format
   152  	if res, ok := map[string]interface{}{
   153  		"email":     eg.r.faker.Email(),
   154  		"hostname":  eg.r.faker.DomainName() + "." + eg.r.faker.DomainSuffix(),
   155  		"date":      time.Unix(int64(eg.r.Int())%1454957045, 0).Format("2006-01-02"), // to obtain a "fixed" rand
   156  		"date-time": time.Unix(int64(eg.r.Int())%1454957045, 0).Format(time.RFC3339), // to obtain a "fixed" rand
   157  		"ipv4":      eg.r.faker.IPv4Address().String(),
   158  		"ipv6":      eg.r.faker.IPv6Address().String(),
   159  		"ip":        eg.r.faker.IPv4Address().String(),
   160  		"uri":       eg.r.faker.URL(),
   161  		"mac": func() string {
   162  			res, err := regen.Generate(`([0-9A-F]{2}-){5}[0-9A-F]{2}`)
   163  			if err != nil {
   164  				return "12-34-56-78-9A-BC"
   165  			}
   166  			return res
   167  		}(),
   168  		"cidr":    "192.168.100.14/24",
   169  		"regexp":  eg.r.faker.Characters(3) + ".*",
   170  		"rfc1123": time.Unix(int64(eg.r.Int())%1454957045, 0).Format(time.RFC1123), // to obtain a "fixed" rand
   171  	}[format]; ok {
   172  		return res
   173  	}
   174  	panic("Validation: unknown format '" + format + "'") // bug
   175  }
   176  
   177  func (eg *exampleGenerator) hasPatternValidation() bool {
   178  	return eg.a.Validation != nil && eg.a.Validation.Pattern != ""
   179  }
   180  
   181  func (eg *exampleGenerator) checkPatternValidation(example interface{}) bool {
   182  	if !eg.hasPatternValidation() {
   183  		return true
   184  	}
   185  	pattern := eg.a.Validation.Pattern
   186  	re, err := regexp.Compile(pattern)
   187  	if err != nil {
   188  		panic("Validation: invalid pattern '" + pattern + "'")
   189  	}
   190  	if !re.MatchString(fmt.Sprint(example)) {
   191  		return false
   192  	}
   193  	return true
   194  }
   195  
   196  // generateValidatedPatternExample generates a random value that satisifies the pattern. Note: if
   197  // multiple patterns are given, only one of them is used. currently, it doesn't support multiple.
   198  func (eg *exampleGenerator) generateValidatedPatternExample() interface{} {
   199  	if !eg.hasPatternValidation() {
   200  		return false
   201  	}
   202  	pattern := eg.a.Validation.Pattern
   203  	example, err := regen.Generate(pattern)
   204  	if err != nil {
   205  		return eg.r.faker.Name()
   206  	}
   207  	return example
   208  }
   209  
   210  func (eg *exampleGenerator) hasMinMaxValidation() bool {
   211  	if eg.a.Validation == nil {
   212  		return false
   213  	}
   214  	return eg.a.Validation.Minimum != nil || eg.a.Validation.Maximum != nil
   215  }
   216  
   217  func (eg *exampleGenerator) checkMinMaxValueValidation(example interface{}) bool {
   218  	if !eg.hasMinMaxValidation() {
   219  		return true
   220  	}
   221  	valid := true
   222  	if min := eg.a.Validation.Minimum; min != nil {
   223  		if v, ok := example.(int); ok && float64(v) < *min {
   224  			valid = false
   225  		} else if v, ok := example.(float64); ok && v < *min {
   226  			valid = false
   227  		}
   228  	}
   229  	if !valid {
   230  		return false
   231  	}
   232  	if max := eg.a.Validation.Maximum; max != nil {
   233  		if v, ok := example.(int); ok && float64(v) > *max {
   234  			return false
   235  		} else if v, ok := example.(float64); ok && v > *max {
   236  			return false
   237  		}
   238  	}
   239  	return true
   240  }
   241  
   242  func (eg *exampleGenerator) generateValidatedMinMaxValueExample() interface{} {
   243  	if !eg.hasMinMaxValidation() {
   244  		return nil
   245  	}
   246  	min, max := math.Inf(1), math.Inf(-1)
   247  	if eg.a.Validation.Minimum != nil {
   248  		min = *eg.a.Validation.Minimum
   249  	}
   250  	if eg.a.Validation.Maximum != nil {
   251  		max = *eg.a.Validation.Maximum
   252  	}
   253  	if math.IsInf(min, 1) {
   254  		if eg.a.Type.Kind() == IntegerKind {
   255  			if max == 0 {
   256  				return int(max) - eg.r.Int()%3
   257  			}
   258  			return eg.r.Int() % int(max)
   259  		}
   260  		return eg.r.Float64() * max
   261  	} else if math.IsInf(max, -1) {
   262  		if eg.a.Type.Kind() == IntegerKind {
   263  			if min == 0 {
   264  				return int(min) + eg.r.Int()%3
   265  			}
   266  			return int(min) + eg.r.Int()%int(min)
   267  		}
   268  		return min + eg.r.Float64()*min
   269  	} else if min < max {
   270  		if eg.a.Type.Kind() == IntegerKind {
   271  			return int(min) + eg.r.Int()%int(max-min)
   272  		}
   273  		return min + eg.r.Float64()*(max-min)
   274  	} else if min == max {
   275  		if eg.a.Type.Kind() == IntegerKind {
   276  			return int(min)
   277  		}
   278  		return min
   279  	}
   280  	panic("Validation: Min > Max")
   281  }