func nestedPropertyStructs(s reflect.Value) map[string]reflect.Value { ret := make(map[string]reflect.Value) var walk func(structValue reflect.Value, prefix string) walk = func(structValue reflect.Value, prefix string) { typ := structValue.Type() for i := 0; i < structValue.NumField(); i++ { field := typ.Field(i) if field.PkgPath != "" { // The field is not exported so just skip it. continue } fieldValue := structValue.Field(i) switch fieldValue.Kind() { case reflect.Bool, reflect.String, reflect.Slice, reflect.Int, reflect.Uint: // Nothing case reflect.Struct: walk(fieldValue, prefix+proptools.PropertyNameForField(field.Name)+".") case reflect.Ptr, reflect.Interface: if !fieldValue.IsNil() { // We leave the pointer intact and zero out the struct that's // pointed to. elem := fieldValue.Elem() if fieldValue.Kind() == reflect.Interface { if elem.Kind() != reflect.Ptr { panic(fmt.Errorf("can't get type of field %q: interface "+ "refers to a non-pointer", field.Name)) } elem = elem.Elem() } if elem.Kind() != reflect.Struct { panic(fmt.Errorf("can't get type of field %q: points to a "+ "non-struct", field.Name)) } nestPoint := prefix + proptools.PropertyNameForField(field.Name) ret[nestPoint] = elem walk(elem, nestPoint+".") } default: panic(fmt.Errorf("unexpected kind for property struct field %q: %s", field.Name, fieldValue.Kind())) } } } walk(s, "") return ret }
func structProperties(structType *ast.StructType) (props []PropertyDocs, err error) { for _, f := range structType.Fields.List { names := f.Names if names == nil { // Anonymous fields have no name, use the type as the name // TODO: hide the name and make the properties show up in the embedding struct if t, ok := f.Type.(*ast.Ident); ok { names = append(names, t) } } for _, n := range names { var name, typ, tag, text string var innerProps []PropertyDocs if n != nil { name = proptools.PropertyNameForField(n.Name) } if f.Doc != nil { text = f.Doc.Text() } if f.Tag != nil { tag, err = strconv.Unquote(f.Tag.Value) if err != nil { return nil, err } } switch a := f.Type.(type) { case *ast.ArrayType: typ = "list of strings" case *ast.InterfaceType: typ = "interface" case *ast.Ident: typ = a.Name case *ast.StructType: innerProps, err = structProperties(a) if err != nil { return nil, err } default: typ = fmt.Sprintf("%T", f.Type) } props = append(props, PropertyDocs{ Name: name, Type: typ, Tag: reflect.StructTag(tag), Text: text, Properties: innerProps, }) } } return props, nil }
func structProperties(structType *ast.StructType) (props []PropertyDocs, err error) { for _, f := range structType.Fields.List { //fmt.Printf("%T %#v\n", f, f) for _, n := range f.Names { var name, typ, tag, text string var innerProps []PropertyDocs if n != nil { name = proptools.PropertyNameForField(n.Name) } if f.Doc != nil { text = f.Doc.Text() } if f.Tag != nil { tag, err = strconv.Unquote(f.Tag.Value) if err != nil { return nil, err } } switch a := f.Type.(type) { case *ast.ArrayType: typ = "list of strings" case *ast.InterfaceType: typ = "interface" case *ast.Ident: typ = a.Name case *ast.StructType: innerProps, err = structProperties(a) if err != nil { return nil, err } default: typ = fmt.Sprintf("%T", f.Type) } props = append(props, PropertyDocs{ Name: name, Type: typ, Tag: reflect.StructTag(tag), Text: text, Properties: innerProps, }) } } return props, nil }
func unpackStructValue(namePrefix string, structValue reflect.Value, propertyMap map[string]*packedProperty, filterKey, filterValue string) []error { structType := structValue.Type() var errs []error for i := 0; i < structValue.NumField(); i++ { fieldValue := structValue.Field(i) field := structType.Field(i) if field.PkgPath != "" { // This is an unexported field, so just skip it. continue } if !fieldValue.CanSet() { panic(fmt.Errorf("field %s is not settable", field.Name)) } // To make testing easier we validate the struct field's type regardless // of whether or not the property was specified in the parsed string. switch kind := fieldValue.Kind(); kind { case reflect.Bool, reflect.String, reflect.Struct: // Do nothing case reflect.Slice: elemType := field.Type.Elem() if elemType.Kind() != reflect.String { panic(fmt.Errorf("field %s is a non-string slice", field.Name)) } case reflect.Interface: if fieldValue.IsNil() { panic(fmt.Errorf("field %s contains a nil interface", field.Name)) } fieldValue = fieldValue.Elem() elemType := fieldValue.Type() if elemType.Kind() != reflect.Ptr { panic(fmt.Errorf("field %s contains a non-pointer interface", field.Name)) } fallthrough case reflect.Ptr: if fieldValue.IsNil() { panic(fmt.Errorf("field %s contains a nil pointer", field.Name)) } fieldValue = fieldValue.Elem() elemType := fieldValue.Type() if elemType.Kind() != reflect.Struct { panic(fmt.Errorf("field %s contains a non-struct pointer", field.Name)) } case reflect.Int, reflect.Uint: if !hasTag(field, "blueprint", "mutated") { panic(fmt.Errorf(`int field %s must be tagged blueprint:"mutated"`, field.Name)) } default: panic(fmt.Errorf("unsupported kind for field %s: %s", field.Name, kind)) } // Get the property value if it was specified. propertyName := namePrefix + proptools.PropertyNameForField(field.Name) packedProperty, ok := propertyMap[propertyName] if !ok { // This property wasn't specified. continue } packedProperty.unpacked = true if hasTag(field, "blueprint", "mutated") { errs = append(errs, &Error{ Err: fmt.Errorf("mutated field %s cannot be set in a Blueprint file", propertyName), Pos: packedProperty.property.Pos, }) if len(errs) >= maxErrors { return errs } continue } if filterKey != "" && !hasTag(field, filterKey, filterValue) { errs = append(errs, &Error{ Err: fmt.Errorf("filtered field %s cannot be set in a Blueprint file", propertyName), Pos: packedProperty.property.Pos, }) if len(errs) >= maxErrors { return errs } continue } var newErrs []error switch kind := fieldValue.Kind(); kind { case reflect.Bool: newErrs = unpackBool(fieldValue, packedProperty.property) case reflect.String: newErrs = unpackString(fieldValue, packedProperty.property) case reflect.Slice: newErrs = unpackSlice(fieldValue, packedProperty.property) case reflect.Ptr, reflect.Interface: fieldValue = fieldValue.Elem() fallthrough case reflect.Struct: localFilterKey, localFilterValue := filterKey, filterValue if k, v, err := HasFilter(field.Tag); err != nil { errs = append(errs, err) if len(errs) >= maxErrors { return errs } } else if k != "" { if filterKey != "" { errs = append(errs, fmt.Errorf("nested filter tag not supported on field %q", field.Name)) if len(errs) >= maxErrors { return errs } } else { localFilterKey, localFilterValue = k, v } } newErrs = unpackStruct(propertyName+".", fieldValue, packedProperty.property, propertyMap, localFilterKey, localFilterValue) } errs = append(errs, newErrs...) if len(errs) >= maxErrors { return errs } } return errs }