github.com/Jeffail/benthos/v3@v3.65.0/lib/output/broker_out_common.go (about) 1 package output 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "strconv" 7 "strings" 8 9 "github.com/Jeffail/benthos/v3/lib/broker" 10 11 "gopkg.in/yaml.v3" 12 ) 13 14 //------------------------------------------------------------------------------ 15 16 type brokerOutputList []Config 17 18 // UnmarshalJSON ensures that when parsing configs that are in a map or slice 19 // the default values are still applied. 20 func (b *brokerOutputList) UnmarshalJSON(bytes []byte) error { 21 genericOutputs := []interface{}{} 22 if err := json.Unmarshal(bytes, &genericOutputs); err != nil { 23 return err 24 } 25 26 outputConfs, err := parseOutputConfsWithDefaults(genericOutputs) 27 if err != nil { 28 return err 29 } 30 31 *b = outputConfs 32 return nil 33 } 34 35 // UnmarshalYAML ensures that when parsing configs that are in a map or slice 36 // the default values are still applied. 37 func (b *brokerOutputList) UnmarshalYAML(unmarshal func(interface{}) error) error { 38 genericOutputs := []interface{}{} 39 if err := unmarshal(&genericOutputs); err != nil { 40 return err 41 } 42 43 outputConfs, err := parseOutputConfsWithDefaults(genericOutputs) 44 if err != nil { 45 return err 46 } 47 48 *b = outputConfs 49 return nil 50 } 51 52 //------------------------------------------------------------------------------ 53 54 // parseOutputConfsWithDefaults takes an array of output configs as 55 // []interface{} and returns an array of output configs with default values in 56 // place of omitted values. This is necessary because when unmarshalling config 57 // files using structs you can pre-populate non-reference type struct fields 58 // with default values, but array objects will lose those defaults. 59 // 60 // In order to ensure that omitted values are set to default we initially parse 61 // the array as interface{} types and then individually apply the defaults by 62 // marshalling and unmarshalling. The correct way to do this would be to use 63 // json.RawMessage, but our config files can contain a range of different 64 // formats that we do not know at this stage (JSON, YAML, etc), therefore we use 65 // the more hacky method as performance is not an issue at this stage. 66 func parseOutputConfsWithDefaults(outConfs []interface{}) ([]Config, error) { 67 outputConfs := []Config{} 68 69 for i, boxedConfig := range outConfs { 70 newConfs := make([]Config, 1) 71 label := broker.GetGenericType(boxedConfig) 72 73 if i > 0 && strings.Index(label, "ditto") == 0 { 74 broker.RemoveGenericType(boxedConfig) 75 76 // Check if there is a ditto multiplier. 77 if len(label) > 5 && label[5] == '_' { 78 if label[6:] == "0" { 79 // This is a special case where we are expressing that 80 // we want to end up with zero duplicates. 81 newConfs = nil 82 } else { 83 n, err := strconv.Atoi(label[6:]) 84 if err != nil { 85 return nil, fmt.Errorf("failed to parse ditto multiplier: %v", err) 86 } 87 newConfs = make([]Config, n) 88 } 89 } else { 90 newConfs = make([]Config, 1) 91 } 92 93 broker.ComplementGenericConfig(boxedConfig, outConfs[i-1]) 94 } 95 96 for _, conf := range newConfs { 97 rawBytes, err := yaml.Marshal(boxedConfig) 98 if err != nil { 99 return nil, err 100 } 101 if err := yaml.Unmarshal(rawBytes, &conf); err != nil { 102 return nil, err 103 } 104 outputConfs = append(outputConfs, conf) 105 } 106 } 107 108 return outputConfs, nil 109 } 110 111 //------------------------------------------------------------------------------