|
15 | 15 | package configcheck
|
16 | 16 |
|
17 | 17 | import (
|
18 |
| - "fmt" |
19 |
| - "reflect" |
20 |
| - "regexp" |
21 |
| - "strings" |
22 |
| - |
23 | 18 | "go.opentelemetry.io/collector/component"
|
| 19 | + "go.opentelemetry.io/collector/config/configtest" |
24 | 20 | "go.opentelemetry.io/collector/consumer/consumererror"
|
25 | 21 | )
|
26 | 22 |
|
27 |
| -// The regular expression for valid config field tag. |
28 |
| -var configFieldTagRegExp = regexp.MustCompile("^[a-z0-9][a-z0-9_]*$") |
29 |
| - |
30 | 23 | // ValidateConfigFromFactories checks if all configurations for the given factories
|
31 | 24 | // are satisfying the patterns used by the collector.
|
32 | 25 | func ValidateConfigFromFactories(factories component.Factories) error {
|
33 | 26 | var errs []error
|
34 | 27 |
|
35 | 28 | for _, factory := range factories.Receivers {
|
36 |
| - if err := ValidateConfig(factory.CreateDefaultConfig()); err != nil { |
| 29 | + if err := configtest.CheckConfigStruct(factory.CreateDefaultConfig()); err != nil { |
37 | 30 | errs = append(errs, err)
|
38 | 31 | }
|
39 | 32 | }
|
40 | 33 | for _, factory := range factories.Processors {
|
41 |
| - if err := ValidateConfig(factory.CreateDefaultConfig()); err != nil { |
| 34 | + if err := configtest.CheckConfigStruct(factory.CreateDefaultConfig()); err != nil { |
42 | 35 | errs = append(errs, err)
|
43 | 36 | }
|
44 | 37 | }
|
45 | 38 | for _, factory := range factories.Exporters {
|
46 |
| - if err := ValidateConfig(factory.CreateDefaultConfig()); err != nil { |
| 39 | + if err := configtest.CheckConfigStruct(factory.CreateDefaultConfig()); err != nil { |
47 | 40 | errs = append(errs, err)
|
48 | 41 | }
|
49 | 42 | }
|
50 | 43 | for _, factory := range factories.Extensions {
|
51 |
| - if err := ValidateConfig(factory.CreateDefaultConfig()); err != nil { |
| 44 | + if err := configtest.CheckConfigStruct(factory.CreateDefaultConfig()); err != nil { |
52 | 45 | errs = append(errs, err)
|
53 | 46 | }
|
54 | 47 | }
|
55 | 48 |
|
56 | 49 | return consumererror.Combine(errs)
|
57 | 50 | }
|
58 |
| - |
59 |
| -// ValidateConfig enforces that given configuration object is following the patterns |
60 |
| -// used by the collector. This ensures consistency between different implementations |
61 |
| -// of components and extensions. It is recommended for implementers of components |
62 |
| -// to call this function on their tests passing the default configuration of the |
63 |
| -// component factory. |
64 |
| -func ValidateConfig(config interface{}) error { |
65 |
| - t := reflect.TypeOf(config) |
66 |
| - if t.Kind() == reflect.Ptr { |
67 |
| - t = t.Elem() |
68 |
| - } |
69 |
| - |
70 |
| - if t.Kind() != reflect.Struct { |
71 |
| - return fmt.Errorf("config must be a struct or a pointer to one, the passed object is a %s", t.Kind()) |
72 |
| - } |
73 |
| - |
74 |
| - return validateConfigDataType(t) |
75 |
| -} |
76 |
| - |
77 |
| -// validateConfigDataType performs a descending validation of the given type. |
78 |
| -// If the type is a struct it goes to each of its fields to check for the proper |
79 |
| -// tags. |
80 |
| -func validateConfigDataType(t reflect.Type) error { |
81 |
| - var errs []error |
82 |
| - |
83 |
| - switch t.Kind() { |
84 |
| - case reflect.Ptr: |
85 |
| - if err := validateConfigDataType(t.Elem()); err != nil { |
86 |
| - errs = append(errs, err) |
87 |
| - } |
88 |
| - case reflect.Struct: |
89 |
| - // Reflect on the pointed data and check each of its fields. |
90 |
| - nf := t.NumField() |
91 |
| - for i := 0; i < nf; i++ { |
92 |
| - f := t.Field(i) |
93 |
| - if err := checkStructFieldTags(f); err != nil { |
94 |
| - errs = append(errs, err) |
95 |
| - } |
96 |
| - } |
97 |
| - default: |
98 |
| - // The config object can carry other types but they are not used when |
99 |
| - // reading the configuration via koanf so ignore them. Basically ignore: |
100 |
| - // reflect.Uintptr, reflect.Chan, reflect.Func, reflect.Interface, and |
101 |
| - // reflect.UnsafePointer. |
102 |
| - } |
103 |
| - |
104 |
| - if err := consumererror.Combine(errs); err != nil { |
105 |
| - return fmt.Errorf( |
106 |
| - "type %q from package %q has invalid config settings: %v", |
107 |
| - t.Name(), |
108 |
| - t.PkgPath(), |
109 |
| - err) |
110 |
| - } |
111 |
| - |
112 |
| - return nil |
113 |
| -} |
114 |
| - |
115 |
| -// checkStructFieldTags inspects the tags of a struct field. |
116 |
| -func checkStructFieldTags(f reflect.StructField) error { |
117 |
| - |
118 |
| - tagValue := f.Tag.Get("mapstructure") |
119 |
| - if tagValue == "" { |
120 |
| - |
121 |
| - // Ignore special types. |
122 |
| - switch f.Type.Kind() { |
123 |
| - case reflect.Interface, reflect.Chan, reflect.Func, reflect.Uintptr, reflect.UnsafePointer: |
124 |
| - // Allow the config to carry the types above, but since they are not read |
125 |
| - // when loading configuration, just ignore them. |
126 |
| - return nil |
127 |
| - } |
128 |
| - |
129 |
| - // Public fields of other types should be tagged. |
130 |
| - chars := []byte(f.Name) |
131 |
| - if len(chars) > 0 && chars[0] >= 'A' && chars[0] <= 'Z' { |
132 |
| - return fmt.Errorf("mapstructure tag not present on field %q", f.Name) |
133 |
| - } |
134 |
| - |
135 |
| - // Not public field, no need to have a tag. |
136 |
| - return nil |
137 |
| - } |
138 |
| - |
139 |
| - tagParts := strings.Split(tagValue, ",") |
140 |
| - if tagParts[0] != "" { |
141 |
| - if tagParts[0] == "-" { |
142 |
| - // Nothing to do, as mapstructure decode skips this field. |
143 |
| - return nil |
144 |
| - } |
145 |
| - } |
146 |
| - |
147 |
| - // Check if squash is specified. |
148 |
| - squash := false |
149 |
| - for _, tag := range tagParts[1:] { |
150 |
| - if tag == "squash" { |
151 |
| - squash = true |
152 |
| - break |
153 |
| - } |
154 |
| - } |
155 |
| - |
156 |
| - if squash { |
157 |
| - // Field was squashed. |
158 |
| - if (f.Type.Kind() != reflect.Struct) && (f.Type.Kind() != reflect.Ptr || f.Type.Elem().Kind() != reflect.Struct) { |
159 |
| - return fmt.Errorf( |
160 |
| - "attempt to squash non-struct type on field %q", f.Name) |
161 |
| - } |
162 |
| - } |
163 |
| - |
164 |
| - switch f.Type.Kind() { |
165 |
| - case reflect.Struct: |
166 |
| - // It is another struct, continue down-level. |
167 |
| - return validateConfigDataType(f.Type) |
168 |
| - |
169 |
| - case reflect.Map, reflect.Slice, reflect.Array: |
170 |
| - // The element of map, array, or slice can be itself a configuration object. |
171 |
| - return validateConfigDataType(f.Type.Elem()) |
172 |
| - |
173 |
| - default: |
174 |
| - fieldTag := tagParts[0] |
175 |
| - if !configFieldTagRegExp.MatchString(fieldTag) { |
176 |
| - return fmt.Errorf( |
177 |
| - "field %q has config tag %q which doesn't satisfy %q", |
178 |
| - f.Name, |
179 |
| - fieldTag, |
180 |
| - configFieldTagRegExp.String()) |
181 |
| - } |
182 |
| - } |
183 |
| - |
184 |
| - return nil |
185 |
| -} |
0 commit comments