/* * SPDX-License-Identifier: AGPL-3.0-only * Copyright (c) 2022-2024, daeuniverse Organization */ package config import ( "bytes" "fmt" "reflect" "strconv" "strings" "github.com/daeuniverse/dae/common" "github.com/daeuniverse/dae/pkg/config_parser" ) // Marshal assume all tokens should be legal, and does not prevent injection attacks. func (c *Config) Marshal(indentSpace int) (b []byte, err error) { m := Marshaller{ IndentSpace: indentSpace, } // Root. v := reflect.ValueOf(*c) t := v.Type() for i := 0; i < v.NumField(); i++ { k, ok := t.Field(i).Tag.Lookup("mapstructure") if !ok { return nil, fmt.Errorf("section %v misses tag mapstructure", t.Field(i).Name) } if err = m.MarshalSection(k, v.Field(i), 0); err != nil { return nil, err } } return m.buf.Bytes(), nil } type Marshaller struct { IndentSpace int IgnoreZero bool buf bytes.Buffer } func (m *Marshaller) Bytes() []byte { return m.buf.Bytes() } func (m *Marshaller) writeLine(depth int, line string) { if depth < 0 { depth = 0 } m.buf.Write(bytes.Repeat([]byte{' '}, depth*m.IndentSpace)) m.buf.WriteString(line) m.buf.WriteString("\n") } func (m *Marshaller) marshalStringList(from reflect.Value, depth int, keyable bool) (err error) { for i := 0; i < from.Len(); i++ { str := from.Index(i) if keyable { tag, afterTag := common.GetTagFromLinkLikePlaintext(str.String()) if len(tag) > 0 { m.writeLine(depth, tag+":"+strconv.Quote(afterTag)) continue } } m.writeLine(depth, strconv.Quote(str.String())) } return nil } func (m *Marshaller) MarshalSection(name string, from reflect.Value, depth int) (err error) { m.writeLine(depth, name+" {") defer m.writeLine(depth, "}") switch from.Kind() { case reflect.Slice: elemType := from.Type().Elem() switch elemType.Kind() { case reflect.String: keyable := false switch elemType { case reflect.TypeOf(KeyableString("")): keyable = true default: } if err = m.marshalStringList(from, depth+1, keyable); err != nil { return err } return nil case reflect.Struct: // "from" is a section list (sections in section). /** from { field1 { ... } field2 { ... } } should be parsed from: from []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 from parse section", from.Type().Elem().String()) } // Scan sections. for i := 0; i < from.Len(); i++ { item := from.Index(i) nameField := item.FieldByName("Name") if nameField.Kind() != reflect.String { return fmt.Errorf("name field of section should be string type") } if err = m.MarshalSection(nameField.String(), item, depth+1); err != nil { return err } } return nil default: goto unsupported } case reflect.Struct: // Section. return m.marshalParam(from, depth+1) default: goto unsupported } panic("code should not reach here") unsupported: return fmt.Errorf("unsupported section type %v", from.Type()) } func (m *Marshaller) marshalLeaf(key string, from reflect.Value, depth int) (err error) { if m.IgnoreZero && from.IsZero() { // Do not marshal zero value. return nil } switch from.Kind() { case reflect.Slice: if from.Len() == 0 { return nil } switch from.Index(0).Interface().(type) { case fmt.Stringer, string, uint8, uint16, uint32, uint64, int8, int16, int32, int64, float32, float64, bool: var vals []string for i := 0; i < from.Len(); i++ { vals = append(vals, fmt.Sprintf("%v", from.Index(i).Interface())) } m.writeLine(depth, key+":"+strconv.Quote(strings.Join(vals, ","))) case *config_parser.Function: var vals []string for i := 0; i < from.Len(); i++ { v := from.Index(i).Interface().(*config_parser.Function) vals = append(vals, v.String(true, true, false)) } m.writeLine(depth, key+":"+strings.Join(vals, "&&")) case KeyableString: m.writeLine(depth, key+" {") if err = m.marshalStringList(from, depth+1, true); err != nil { return err } m.writeLine(depth, "}") default: return fmt.Errorf("unknown leaf array type: %v", from.Type()) } default: switch val := from.Interface().(type) { case fmt.Stringer, string, uint8, uint16, uint32, uint64, int8, int16, int32, int64, float32, float64, bool: m.writeLine(depth, key+":"+strconv.Quote(fmt.Sprintf("%v", val))) case *config_parser.Function: m.writeLine(depth, key+":"+val.String(true, true, false)) default: return fmt.Errorf("unknown leaf type: %T", val) } } return nil } func (m *Marshaller) marshalParam(from reflect.Value, depth int) (err error) { if from.Kind() != reflect.Struct { return fmt.Errorf("marshalParam can only marshal struct") } // Marshal section. typ := from.Type() for i := 0; i < from.NumField(); i++ { field := from.Field(i) structField := typ.Field(i) key, ok := structField.Tag.Lookup("mapstructure") if !ok { return fmt.Errorf("tag mapstructure is required") } // Reserved field. if key == "_" { switch structField.Name { case "Name": case "Rules": // Expand. rules, ok := field.Interface().([]*config_parser.RoutingRule) if !ok { return fmt.Errorf("unexpected Rules type: %v", field.Type()) } for _, r := range rules { m.writeLine(depth, r.String(false, true, true)) } default: return fmt.Errorf("unknown reserved field: %v", structField.Name) } continue } // Section(s) field. if field.Kind() == reflect.Struct || (field.Kind() == reflect.Slice && field.Type().Elem().Kind() == reflect.Struct) { if err = m.MarshalSection(key, field, depth); err != nil { return err } continue } // Normal field. if err = m.marshalLeaf(key, field, depth); err != nil { return err } } return nil }