go.uber.org/yarpc@v1.72.1/yarpcconfig/decode.go (about) 1 // Copyright (c) 2022 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package yarpcconfig 22 23 import ( 24 "errors" 25 "fmt" 26 27 "github.com/uber-go/mapdecode" 28 "go.uber.org/yarpc" 29 "go.uber.org/yarpc/internal/config" 30 "go.uber.org/zap/zapcore" 31 ) 32 33 type yarpcConfig struct { 34 Inbounds inbounds `config:"inbounds"` 35 Outbounds clientConfigs `config:"outbounds"` 36 Transports map[string]config.AttributeMap `config:"transports"` 37 Logging logging `config:"logging"` 38 Metrics metrics `config:"metrics"` 39 } 40 41 // metrics allows configuring the way metrics are emitted from YAML 42 type metrics struct { 43 TagsBlocklist []string `config:"tagsBlocklist"` 44 } 45 46 // Fills values from this object into the provided YARPC config. 47 func (m *metrics) fill(cfg *yarpc.Config) { 48 cfg.Metrics.TagsBlocklist = m.TagsBlocklist 49 } 50 51 // logging allows configuring the log levels from YAML. 52 type logging struct { 53 Levels struct { 54 // Defaults regardless of direction. 55 Success *zapLevel `config:"success"` 56 Failure *zapLevel `config:"failure"` 57 ApplicationError *zapLevel `config:"applicationError"` 58 ServerError *zapLevel `config:"serverError"` 59 ClientError *zapLevel `config:"clientError"` 60 61 // Directional overrides. 62 Inbound levels `config:"inbound"` 63 Outbound levels `config:"outbound"` 64 } `config:"levels"` 65 } 66 67 type levels struct { 68 Success *zapLevel `config:"success"` 69 Failure *zapLevel `config:"failure"` 70 ApplicationError *zapLevel `config:"applicationError"` 71 ServerError *zapLevel `config:"serverError"` 72 ClientError *zapLevel `config:"clientError"` 73 } 74 75 // Fills values from this object into the provided YARPC config. 76 func (l *logging) fill(cfg *yarpc.Config) { 77 cfg.Logging.Levels.Success = (*zapcore.Level)(l.Levels.Success) 78 cfg.Logging.Levels.Failure = (*zapcore.Level)(l.Levels.Failure) 79 cfg.Logging.Levels.ApplicationError = (*zapcore.Level)(l.Levels.ApplicationError) 80 cfg.Logging.Levels.ServerError = (*zapcore.Level)(l.Levels.ServerError) 81 cfg.Logging.Levels.ClientError = (*zapcore.Level)(l.Levels.ClientError) 82 83 l.Levels.Inbound.fill(&cfg.Logging.Levels.Inbound) 84 l.Levels.Outbound.fill(&cfg.Logging.Levels.Outbound) 85 } 86 87 func (l *levels) fill(cfg *yarpc.DirectionalLogLevelConfig) { 88 cfg.Success = (*zapcore.Level)(l.Success) 89 cfg.Failure = (*zapcore.Level)(l.Failure) 90 cfg.ApplicationError = (*zapcore.Level)(l.ApplicationError) 91 cfg.ServerError = (*zapcore.Level)(l.ServerError) 92 cfg.ClientError = (*zapcore.Level)(l.ClientError) 93 } 94 95 type zapLevel zapcore.Level 96 97 // mapdecode doesn't suport encoding.TextMarhsaler by default so we have to do 98 // this manually. 99 func (l *zapLevel) Decode(into mapdecode.Into) error { 100 var s string 101 if err := into(&s); err != nil { 102 return fmt.Errorf("could not decode Zap log level: %v", err) 103 } 104 105 err := (*zapcore.Level)(l).UnmarshalText([]byte(s)) 106 if err != nil { 107 return fmt.Errorf("could not decode Zap log level: %v", err) 108 } 109 return err 110 } 111 112 type inbounds []inbound 113 114 func (is *inbounds) Decode(into mapdecode.Into) error { 115 var items map[string]inbound 116 if err := into(&items); err != nil { 117 return fmt.Errorf("failed to decode inbound items: %v", err) 118 } 119 120 for k, v := range items { 121 if v.Type == "" { 122 v.Type = k 123 } 124 *is = append(*is, v) 125 } 126 return nil 127 } 128 129 type inbound struct { 130 Type string 131 Disabled bool 132 Attributes config.AttributeMap 133 } 134 135 func (i *inbound) Decode(into mapdecode.Into) error { 136 if err := into(&i.Attributes); err != nil { 137 return fmt.Errorf("failed to decode inbound: %v", err) 138 } 139 140 var err error 141 i.Type, err = i.Attributes.PopString("type") 142 if err != nil { 143 return fmt.Errorf(`failed to read attribute "type" of inbound: %v`, err) 144 } 145 i.Disabled, err = i.Attributes.PopBool("disabled") 146 if err != nil { 147 return fmt.Errorf(`failed to read attribute "disabled" of inbound: %v`, err) 148 } 149 150 return nil 151 } 152 153 type clientConfigs map[string]outbounds 154 155 func (cc *clientConfigs) Decode(into mapdecode.Into) error { 156 var items map[string]outbounds 157 if err := into(&items); err != nil { 158 return fmt.Errorf("failed to decode outbound items: %v", err) 159 } 160 161 for k, v := range items { 162 if v.Service == "" { 163 v.Service = k 164 } 165 items[k] = v 166 } 167 *cc = items 168 return nil 169 } 170 171 type outbounds struct { 172 Service string 173 174 // Either (Unary and/or Oneway) will be set or Implicit will be set. For 175 // the latter case, we need to only use those configurations that that 176 // transport supports. 177 Unary *outbound 178 Oneway *outbound 179 Stream *outbound 180 Implicit *outbound 181 } 182 183 func (o *outbounds) Decode(into mapdecode.Into) error { 184 var attrs config.AttributeMap 185 if err := into(&attrs); err != nil { 186 return fmt.Errorf("failed to decode outbound configuration: %v", err) 187 } 188 189 var err error 190 o.Service, err = attrs.PopString("service") 191 if err != nil { 192 return fmt.Errorf("failed to read service name for outbound: %v", err) 193 } 194 195 hasUnary, err := attrs.Pop("unary", &o.Unary) 196 if err != nil { 197 return fmt.Errorf("failed to unary outbound configuration: %v", err) 198 } 199 200 hasOneway, err := attrs.Pop("oneway", &o.Oneway) 201 if err != nil { 202 return fmt.Errorf("failed to oneway outbound configuration: %v", err) 203 } 204 205 hasStream, err := attrs.Pop("stream", &o.Stream) 206 if err != nil { 207 return fmt.Errorf("failed to stream outbound configuration: %v", err) 208 } 209 210 if hasUnary || hasOneway || hasStream { 211 // No more attributes should be remaining 212 var empty struct{} 213 if err := attrs.Decode(&empty); err != nil { 214 return fmt.Errorf( 215 "too many attributes in explicit outbound configuration: %v", err) 216 } 217 return nil 218 } 219 220 if err := attrs.Decode(&o.Implicit); err != nil { 221 return fmt.Errorf("failed to decode implicit outbound configuration: %v", err) 222 } 223 return nil 224 } 225 226 type outbound struct { 227 Type string 228 Attributes config.AttributeMap 229 } 230 231 func (o *outbound) Decode(into mapdecode.Into) error { 232 var cfg map[string]config.AttributeMap 233 if err := into(&cfg); err != nil { 234 return fmt.Errorf("failed to decode outbound: %v", err) 235 } 236 237 switch len(cfg) { 238 case 0: 239 return errors.New("failed to decode outbound: an outbound type is required") 240 case 1: 241 // Move along 242 default: 243 return errors.New("failed to decode outbound: " + 244 "at most one outbound type may be specified") 245 } 246 247 for k, attrs := range cfg { 248 o.Type = k 249 o.Attributes = attrs 250 } 251 252 return nil 253 }