github.com/colincross/blueprint@v0.0.0-20150626231830-9c067caf2eb5/unpack.go (about) 1 // Copyright 2014 Google Inc. All rights reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package blueprint 16 17 import ( 18 "fmt" 19 "reflect" 20 "strconv" 21 "strings" 22 23 "github.com/google/blueprint/parser" 24 "github.com/google/blueprint/proptools" 25 ) 26 27 type packedProperty struct { 28 property *parser.Property 29 unpacked bool 30 } 31 32 func unpackProperties(propertyDefs []*parser.Property, 33 propertiesStructs ...interface{}) (map[string]*parser.Property, []error) { 34 35 propertyMap := make(map[string]*packedProperty) 36 errs := buildPropertyMap("", propertyDefs, propertyMap) 37 if len(errs) > 0 { 38 return nil, errs 39 } 40 41 for _, properties := range propertiesStructs { 42 propertiesValue := reflect.ValueOf(properties) 43 if propertiesValue.Kind() != reflect.Ptr { 44 panic("properties must be a pointer to a struct") 45 } 46 47 propertiesValue = propertiesValue.Elem() 48 if propertiesValue.Kind() != reflect.Struct { 49 panic("properties must be a pointer to a struct") 50 } 51 52 newErrs := unpackStructValue("", propertiesValue, propertyMap, "", "") 53 errs = append(errs, newErrs...) 54 55 if len(errs) >= maxErrors { 56 return nil, errs 57 } 58 } 59 60 // Report any properties that didn't have corresponding struct fields as 61 // errors. 62 result := make(map[string]*parser.Property) 63 for name, packedProperty := range propertyMap { 64 result[name] = packedProperty.property 65 if !packedProperty.unpacked { 66 err := &Error{ 67 Err: fmt.Errorf("unrecognized property %q", name), 68 Pos: packedProperty.property.Pos, 69 } 70 errs = append(errs, err) 71 } 72 } 73 74 if len(errs) > 0 { 75 return nil, errs 76 } 77 78 return result, nil 79 } 80 81 func buildPropertyMap(namePrefix string, propertyDefs []*parser.Property, 82 propertyMap map[string]*packedProperty) (errs []error) { 83 84 for _, propertyDef := range propertyDefs { 85 name := namePrefix + propertyDef.Name.Name 86 if first, present := propertyMap[name]; present { 87 if first.property == propertyDef { 88 // We've already added this property. 89 continue 90 } 91 92 errs = append(errs, &Error{ 93 Err: fmt.Errorf("property %q already defined", name), 94 Pos: propertyDef.Pos, 95 }) 96 errs = append(errs, &Error{ 97 Err: fmt.Errorf("<-- previous definition here"), 98 Pos: first.property.Pos, 99 }) 100 if len(errs) >= maxErrors { 101 return errs 102 } 103 continue 104 } 105 106 propertyMap[name] = &packedProperty{ 107 property: propertyDef, 108 unpacked: false, 109 } 110 111 // We intentionally do not rescursively add MapValue properties to the 112 // property map here. Instead we add them when we encounter a struct 113 // into which they can be unpacked. We do this so that if we never 114 // encounter such a struct then the "unrecognized property" error will 115 // be reported only once for the map property and not for each of its 116 // sub-properties. 117 } 118 119 return 120 } 121 122 func unpackStructValue(namePrefix string, structValue reflect.Value, 123 propertyMap map[string]*packedProperty, filterKey, filterValue string) []error { 124 125 structType := structValue.Type() 126 127 var errs []error 128 for i := 0; i < structValue.NumField(); i++ { 129 fieldValue := structValue.Field(i) 130 field := structType.Field(i) 131 132 if field.PkgPath != "" { 133 // This is an unexported field, so just skip it. 134 continue 135 } 136 137 if !fieldValue.CanSet() { 138 panic(fmt.Errorf("field %s is not settable", field.Name)) 139 } 140 141 // To make testing easier we validate the struct field's type regardless 142 // of whether or not the property was specified in the parsed string. 143 switch kind := fieldValue.Kind(); kind { 144 case reflect.Bool, reflect.String, reflect.Struct: 145 // Do nothing 146 case reflect.Slice: 147 elemType := field.Type.Elem() 148 if elemType.Kind() != reflect.String { 149 panic(fmt.Errorf("field %s is a non-string slice", field.Name)) 150 } 151 case reflect.Interface: 152 if fieldValue.IsNil() { 153 panic(fmt.Errorf("field %s contains a nil interface", 154 field.Name)) 155 } 156 fieldValue = fieldValue.Elem() 157 elemType := fieldValue.Type() 158 if elemType.Kind() != reflect.Ptr { 159 panic(fmt.Errorf("field %s contains a non-pointer interface", 160 field.Name)) 161 } 162 fallthrough 163 case reflect.Ptr: 164 if fieldValue.IsNil() { 165 panic(fmt.Errorf("field %s contains a nil pointer", 166 field.Name)) 167 } 168 fieldValue = fieldValue.Elem() 169 elemType := fieldValue.Type() 170 if elemType.Kind() != reflect.Struct { 171 panic(fmt.Errorf("field %s contains a non-struct pointer", 172 field.Name)) 173 } 174 175 case reflect.Int, reflect.Uint: 176 if !hasTag(field, "blueprint", "mutated") { 177 panic(fmt.Errorf(`int field %s must be tagged blueprint:"mutated"`, field.Name)) 178 } 179 180 default: 181 panic(fmt.Errorf("unsupported kind for field %s: %s", 182 field.Name, kind)) 183 } 184 185 // Get the property value if it was specified. 186 propertyName := namePrefix + proptools.PropertyNameForField(field.Name) 187 packedProperty, ok := propertyMap[propertyName] 188 if !ok { 189 // This property wasn't specified. 190 continue 191 } 192 193 packedProperty.unpacked = true 194 195 if hasTag(field, "blueprint", "mutated") { 196 errs = append(errs, 197 &Error{ 198 Err: fmt.Errorf("mutated field %s cannot be set in a Blueprint file", propertyName), 199 Pos: packedProperty.property.Pos, 200 }) 201 if len(errs) >= maxErrors { 202 return errs 203 } 204 continue 205 } 206 207 if filterKey != "" && !hasTag(field, filterKey, filterValue) { 208 errs = append(errs, 209 &Error{ 210 Err: fmt.Errorf("filtered field %s cannot be set in a Blueprint file", propertyName), 211 Pos: packedProperty.property.Pos, 212 }) 213 if len(errs) >= maxErrors { 214 return errs 215 } 216 continue 217 } 218 219 var newErrs []error 220 221 switch kind := fieldValue.Kind(); kind { 222 case reflect.Bool: 223 newErrs = unpackBool(fieldValue, packedProperty.property) 224 case reflect.String: 225 newErrs = unpackString(fieldValue, packedProperty.property) 226 case reflect.Slice: 227 newErrs = unpackSlice(fieldValue, packedProperty.property) 228 case reflect.Ptr, reflect.Interface: 229 fieldValue = fieldValue.Elem() 230 fallthrough 231 case reflect.Struct: 232 localFilterKey, localFilterValue := filterKey, filterValue 233 if k, v, err := HasFilter(field.Tag); err != nil { 234 errs = append(errs, err) 235 if len(errs) >= maxErrors { 236 return errs 237 } 238 } else if k != "" { 239 if filterKey != "" { 240 errs = append(errs, fmt.Errorf("nested filter tag not supported on field %q", 241 field.Name)) 242 if len(errs) >= maxErrors { 243 return errs 244 } 245 } else { 246 localFilterKey, localFilterValue = k, v 247 } 248 } 249 newErrs = unpackStruct(propertyName+".", fieldValue, 250 packedProperty.property, propertyMap, localFilterKey, localFilterValue) 251 } 252 errs = append(errs, newErrs...) 253 if len(errs) >= maxErrors { 254 return errs 255 } 256 } 257 258 return errs 259 } 260 261 func unpackBool(boolValue reflect.Value, property *parser.Property) []error { 262 if property.Value.Type != parser.Bool { 263 return []error{ 264 fmt.Errorf("%s: can't assign %s value to %s property %q", 265 property.Value.Pos, property.Value.Type, parser.Bool, 266 property.Name), 267 } 268 } 269 boolValue.SetBool(property.Value.BoolValue) 270 return nil 271 } 272 273 func unpackString(stringValue reflect.Value, 274 property *parser.Property) []error { 275 276 if property.Value.Type != parser.String { 277 return []error{ 278 fmt.Errorf("%s: can't assign %s value to %s property %q", 279 property.Value.Pos, property.Value.Type, parser.String, 280 property.Name), 281 } 282 } 283 stringValue.SetString(property.Value.StringValue) 284 return nil 285 } 286 287 func unpackSlice(sliceValue reflect.Value, property *parser.Property) []error { 288 if property.Value.Type != parser.List { 289 return []error{ 290 fmt.Errorf("%s: can't assign %s value to %s property %q", 291 property.Value.Pos, property.Value.Type, parser.List, 292 property.Name), 293 } 294 } 295 296 var list []string 297 for _, value := range property.Value.ListValue { 298 if value.Type != parser.String { 299 // The parser should not produce this. 300 panic("non-string value found in list") 301 } 302 list = append(list, value.StringValue) 303 } 304 305 sliceValue.Set(reflect.ValueOf(list)) 306 return nil 307 } 308 309 func unpackStruct(namePrefix string, structValue reflect.Value, 310 property *parser.Property, propertyMap map[string]*packedProperty, 311 filterKey, filterValue string) []error { 312 313 if property.Value.Type != parser.Map { 314 return []error{ 315 fmt.Errorf("%s: can't assign %s value to %s property %q", 316 property.Value.Pos, property.Value.Type, parser.Map, 317 property.Name), 318 } 319 } 320 321 errs := buildPropertyMap(namePrefix, property.Value.MapValue, propertyMap) 322 if len(errs) > 0 { 323 return errs 324 } 325 326 return unpackStructValue(namePrefix, structValue, propertyMap, filterKey, filterValue) 327 } 328 329 func hasTag(field reflect.StructField, name, value string) bool { 330 tag := field.Tag.Get(name) 331 for _, entry := range strings.Split(tag, ",") { 332 if entry == value { 333 return true 334 } 335 } 336 337 return false 338 } 339 340 func HasFilter(field reflect.StructTag) (k, v string, err error) { 341 tag := field.Get("blueprint") 342 for _, entry := range strings.Split(tag, ",") { 343 if strings.HasPrefix(entry, "filter") { 344 if !strings.HasPrefix(entry, "filter(") || !strings.HasSuffix(entry, ")") { 345 return "", "", fmt.Errorf("unexpected format for filter %q: missing ()", entry) 346 } 347 entry = strings.TrimPrefix(entry, "filter(") 348 entry = strings.TrimSuffix(entry, ")") 349 350 s := strings.Split(entry, ":") 351 if len(s) != 2 { 352 return "", "", fmt.Errorf("unexpected format for filter %q: expected single ':'", entry) 353 } 354 k = s[0] 355 v, err = strconv.Unquote(s[1]) 356 if err != nil { 357 return "", "", fmt.Errorf("unexpected format for filter %q: %s", entry, err.Error()) 358 } 359 return k, v, nil 360 } 361 } 362 363 return "", "", nil 364 }