github.com/shogo82148/goa-v1@v1.6.2/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 satisfied 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 satisfied, 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 satisfies 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 }