/* * SPDX-License-Identifier: AGPL-3.0-only * Copyright (c) 2022-2024, daeuniverse Organization */ package config import ( "fmt" "reflect" "strings" "github.com/daeuniverse/dae/common" "github.com/daeuniverse/dae/pkg/config_parser" ) func StringListParser(to reflect.Value, section *config_parser.Section) error { if to.Kind() != reflect.Pointer { return fmt.Errorf("StringListParser can only unmarshal section to *[]string") } to = to.Elem() if to.Type() != reflect.TypeOf([]string{}) && !(to.Kind() == reflect.Slice && to.Type().Elem().Kind() == reflect.String) { return fmt.Errorf("StringListParser can only unmarshal section to *[]string") } for _, item := range section.Items { switch itemVal := item.Value.(type) { case *config_parser.Param: to.Set(reflect.Append(to, reflect.ValueOf(itemVal.String(true, false)).Convert(to.Type().Elem()))) default: return fmt.Errorf("section %v does not support type %v: %v", section.Name, item.Type.String(), item.String(false, false)) } } return nil } func ParamParser(to reflect.Value, section *config_parser.Section, ignoreType []reflect.Type) error { if to.Kind() != reflect.Pointer { return fmt.Errorf("ParamParser can only unmarshal section to *struct") } to = to.Elem() if to.Kind() != reflect.Struct { return fmt.Errorf("ParamParser can only unmarshal section to struct") } // keyToField is for further parsing use. type Field struct { Val reflect.Value Annotation reflect.Value Index int Set bool Repeatable bool } var keyToField = make(map[string]*Field) tot := to.Type() for i := 0; i < to.NumField(); i++ { field := to.Field(i) structField := tot.Field(i) // Set up key to field mapping. key, ok := structField.Tag.Lookup("mapstructure") if !ok { return fmt.Errorf("field \"%v\" has no mapstructure tag", structField.Name) } if key == "_" { // omit continue } annotationSF, annotatable := tot.FieldByName(structField.Name + "Annotation") var annotation reflect.Value if annotatable && annotationSF.Tag.Get("mapstructure") == "_" { annotation = to.FieldByName(annotationSF.Name) } _, repeatable := structField.Tag.Lookup("repeatable") keyToField[key] = &Field{Val: field, Annotation: annotation, Index: i, Repeatable: repeatable} // Fill in default value before parsing section. defaultValue, ok := structField.Tag.Lookup("default") if ok { // Can we assign? if field.Kind() == reflect.Interface || field.Type() == reflect.TypeOf(defaultValue) { field.Set(reflect.ValueOf(defaultValue)) // Can we fuzzy decode? } else if !common.FuzzyDecode(field.Addr().Interface(), defaultValue) { return fmt.Errorf(`failed to decode default value of "%v"`, structField.Name) } } } // Convert ignoreType from list to set. ignoreTypeSet := make(map[reflect.Type]struct{}) for _, typ := range ignoreType { ignoreTypeSet[typ] = struct{}{} } // Parse section. for _, item := range section.Items { switch itemVal := item.Value.(type) { case *config_parser.Param: if itemVal.Key == "" { return fmt.Errorf("unsupported text without a key: %v", itemVal.String(true, false)) } field, ok := keyToField[itemVal.Key] if !ok { return fmt.Errorf("unexpected key: %v", itemVal.Key) } if itemVal.AndFunctions != nil { // AndFunctions. // If field is interface{} or types equal, we can assign. if field.Val.Kind() == reflect.Interface || field.Val.Type() == reflect.TypeOf(itemVal.AndFunctions) { field.Val.Set(reflect.ValueOf(itemVal.AndFunctions)) if field.Annotation.IsValid() { if field.Annotation.Type() != reflect.TypeOf(itemVal.Annotation) { return fmt.Errorf("[CODE BUG]: unmatched annotation type") } field.Annotation.Set(reflect.ValueOf(itemVal.Annotation)) } } else if field.Repeatable && field.Val.Type() == reflect.SliceOf(reflect.TypeOf(itemVal.AndFunctions)) { // If field is slice and repeatable, and slice element types match, we can append. field.Val.Set(reflect.Append(field.Val, reflect.ValueOf(itemVal.AndFunctions))) if field.Annotation.IsValid() { if field.Annotation.Type() != reflect.SliceOf(reflect.TypeOf(itemVal.Annotation)) { return fmt.Errorf("[CODE BUG]: unmatched annotation type") } // We also append if `itemVal.Annotation == nil` because we want the same annotation length with the field's. field.Annotation.Set(reflect.Append(field.Annotation, reflect.ValueOf(itemVal.Annotation))) } } else { return fmt.Errorf("failed to parse \"%v\": value \"%v\" cannot be convert to %v", itemVal.Key, itemVal.Val, field.Val.Type().String()) } } else { // String value. switch field.Val.Kind() { case reflect.Interface: // Field is interface{}, we can assign. field.Val.Set(reflect.ValueOf(itemVal.Val)) case reflect.Slice: // Field is not interface{}, we can decode. values := strings.Split(itemVal.Val, ",") if len(values) > 0 && !field.Set { // Clear default value to avoid appending values to it. field.Val.Set(reflect.Zero(field.Val.Type())) } for _, value := range values { vPointerNew := reflect.New(field.Val.Type().Elem()) if !common.FuzzyDecode(vPointerNew.Interface(), value) { return fmt.Errorf("failed to parse \"%v\": value \"%v\" cannot be convert to %v", itemVal.Key, itemVal.Val, field.Val.Type().Elem().String()) } field.Val.Set(reflect.Append(field.Val, vPointerNew.Elem())) } default: // Field is not interface{}, we can decode. if !common.FuzzyDecode(field.Val.Addr().Interface(), itemVal.Val) { return fmt.Errorf("failed to parse \"%v\": value \"%v\" cannot be convert to %v", itemVal.Key, itemVal.Val, field.Val.Type().String()) } } } field.Set = true case *config_parser.Section: // Named section config item. field, ok := keyToField[itemVal.Name] if !ok { return fmt.Errorf("unexpected key: %v", itemVal.Name) } if err := SectionParser(field.Val.Addr(), itemVal); err != nil { return fmt.Errorf("failed to parse %v: %w", itemVal.Name, err) } field.Set = true case *config_parser.RoutingRule: // Assign. "to" should have field "Rules". structField, ok := to.Type().FieldByName("Rules") if !ok || structField.Type != reflect.TypeOf([]*config_parser.RoutingRule{}) { return fmt.Errorf("cannot use routing rule in this context: %v", itemVal.String(true, false, false)) } if structField.Tag.Get("mapstructure") != "_" { return fmt.Errorf("a []*RoutingRule field \"Rules\" with mapstructure:\"_\" is required in struct %v to parse section", to.Type().String()) } field := to.FieldByName("Rules") field.Set(reflect.Append(field, reflect.ValueOf(itemVal))) default: if _, ignore := ignoreTypeSet[reflect.TypeOf(itemVal)]; !ignore { return fmt.Errorf("unexpected type %v: %v", item.Type.String(), item.String(false, false)) } } } // Check required. for key, field := range keyToField { if field.Set { continue } t := to.Type().Field(field.Index) _, required := t.Tag.Lookup("required") if required { return fmt.Errorf(`section "%v" requires param "%v" but not found`, section.Name, key) } } return nil } func SectionParser(to reflect.Value, section *config_parser.Section) error { if to.Kind() != reflect.Pointer { return fmt.Errorf("SectionParser can only unmarshal section to a pointer") } to = to.Elem() switch to.Kind() { case reflect.Slice: elemType := to.Type().Elem() switch elemType.Kind() { case reflect.String: return StringListParser(to.Addr(), section) case reflect.Struct: // "to" is a section list (sections in section). /** to { field1 { ... } field2 { ... } } should be parsed to: to []struct { Name string `mapstructure: "_"` ... } */ // The struct should contain Name. nameStructField, ok := elemType.FieldByName("Name") if !ok || nameStructField.Type.Kind() != reflect.String || nameStructField.Tag.Get("mapstructure") != "_" { return fmt.Errorf("a string field \"Name\" with mapstructure:\"_\" is required in struct %v to parse section", to.Type().Elem().String()) } // Scan sections. for _, item := range section.Items { elem := reflect.New(elemType).Elem() switch itemVal := item.Value.(type) { case *config_parser.Section: elem.FieldByName("Name").SetString(itemVal.Name) if err := SectionParser(elem.Addr(), itemVal); err != nil { return fmt.Errorf("error when parse \"%v\": %w", itemVal.Name, err) } to.Set(reflect.Append(to, elem)) default: return fmt.Errorf("unmatched type: %v -> %v", item.Type.String(), elemType) } } return nil default: goto unsupported } case reflect.Struct: // Section. return ParamParser(to.Addr(), section, nil) default: goto unsupported } panic("code should not reach here") unsupported: return fmt.Errorf("unsupported section type %v", to.Type()) }