├── .gitignore ├── LICENSE ├── README.md ├── errors.go ├── field.go ├── fieldset.go ├── go.mod ├── go.sum ├── input.go ├── interfaces.go ├── keyvalue.go ├── labeler.go ├── labeler_test.go ├── marshalers.go ├── meta.go ├── options.go ├── subject.go ├── tag.go └── unmarshalers.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Chance 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # labeler 🏷 [![Go Report Card](https://goreportcard.com/badge/github.com/chanced/labeler)](https://goreportcard.com/report/github.com/chanced/labeler) 2 | 3 | A Go package for marshaling and unmarshaling `map[string]string` with struct tags. 4 | 5 | Requires go version 1.13+ for `errors.As` and `errors.Is` 6 | 7 | ```bash 8 | go get github.com/chanced/labeler 9 | ``` 10 | 11 | ```go 12 | package main 13 | import ( 14 | "fmt" 15 | "github.com/chanced/labeler" 16 | ) 17 | 18 | // The simplest implementation uses a container for labels, 19 | // denoted with a tag marked as `label:"*"` 20 | type Struct struct { 21 | Labels map[string]string `label:"*"` 22 | Field string `label:"field"` 23 | } 24 | 25 | func main() { 26 | labels := map[string]string{"field": "val"} 27 | v := Struct{} 28 | labeler.Unmarshal(labels, &v) 29 | result, err := labeler.Marshal(&v) 30 | 31 | _ = err 32 | fmt.Println("v.Field: ", v.Field) 33 | for key, value := range labels { 34 | fmt.Printf("labels[%s]: %s result[%s]: %s\n", key, labels[key], key, result[key]) 35 | } 36 | } 37 | ``` 38 | 39 | - [Motivation](#motivation) 40 | - [Value: `interface{}` defined](#value-interface-defined) 41 | - [Fields](#fields) 42 | - [Labels](#labels) 43 | - [Unmarshal Input](#input-unmarshal) 44 | - [Labeler Instance](#labeler-instance) 45 | - [Examples](#examples) 46 | - [Basic with accessor / mutator for labels](#basic-example-with-accessor-mutator-for-labels) 47 | - [With an enum](#example-with-an-enum) 48 | - [Using a container tag](#example-using-a-container-tag) 49 | - [Using multiple tags](#example-using-multiple-tags) 50 | - [Options](#options) 51 | - [Settings](#settings) 52 | - [Tokens](#tokens) 53 | - [Notes](#notes) 54 | - [Prior Art](#prior-art) 55 | - [License (MIT)](#license) 56 | 57 | ## Motivation 58 | 59 | I am developing a project on Google Cloud Platform. Resources on GCP can have 60 | labels, which I use in a number of ways to classify and organize with. The gRPC 61 | client responses come with a `GetLabels()map[string]string` method. This project 62 | was built as a means to interpret `map[string]string` into a well-defined `struct`. 63 | 64 | The package has worked really well for config values coming in from Environ & elsewhere. 65 | 66 | ## Value: `interface{}` defined 67 | 68 | Both Marshal and Unmarshal accept `v interface{}`, the value to marshal from or unmarshal 69 | into. `v` must be pointer to a `struct` or a `type` that implements 70 | `labeler.MarshalWithOpts`, `labeler.Marshal`, `labeler.UnmarshalWithOpts`, or 71 | `labeler.Unmarshal` respectively. 72 | 73 | ### Fields 74 | 75 | labeler is fairly flexible when it comes to what all you can tag. It supports the following types: 76 | 77 | | Interface / Type | Signature | Usage | 78 | | :---------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------- | --------: | 79 | | `labeler.MarshalerWithOpts` | `MarshalLabels(o labeler.Options) map[string]string` | Marshal | 80 | | `labeler.Marshaler` | `MarshalLabels() map[string]string` | Marshal | 81 | | `fmt.Stringer` | `String() string` | Marshal | 82 | | `encoding.TextMarshaler` | `MarshalText() (text []byte, err error)` | Marshal | 83 | | `labeler.UnmarshalerWithOpts` | `UnmarshalLabels(v map[string]string, opts Options) error` | Unmarshal | 84 | | `labeler.Unmarshaler` | `UnmarshalLabels(l map[string]string) error` | Unmarshal | 85 | | `labeler.Stringee` | `FromString(s string) error` | Unmarshal | 86 | | `encoding.TextUnmarshaler` | `UnmarshalText(text []byte) error` | Unmarshal | 87 | | `struct` | can either implement any of the above interfaces or have fields with tags. Supports `n` level of nesting | Both | 88 | | basic types | `string`, `bool`, `int`, `int64`, `int32`, `int16`, `int8`, `float64`, `float32`, `uint`, `uint64`, `uint32`, `uint16`, `uint8`, `complex128`, `complex64`, | Both | 89 | | time | `time.Time`, `time.Duration` | Both | 90 | | pointer | pointer to any of the above | Both | 91 | | slices & arrays | slices / arrays composed of any type above | Both | 92 | 93 | ### Labels 94 | 95 | When unmarshaling, labeler needs a way to persist labels, regardless of whether or not they 96 | have been assigned to tagged fields. By default, labeler will retain all labels unless 97 | `Options.KeepLabels` is set to `false` (See [Options](#options)). This is to ensure 98 | data integrity for labels that have not been unmarshaled into fields. 99 | 100 | Your available choices for `v` are: 101 | 102 | | Interface / Type | Signature | 103 | | :------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | 104 | | `labeler.UnmarshalerWithOpts` | `UnmarshalLabels(v map[string]string, opts Options) error` | 105 | | `labeler.Unmarshaler` | `UnmarshalLabels(l map[string]string) error` | 106 | | `labeler.Labelee` | `SetLabels(labels map[string]string)` | 107 | | `labeler.StrictLabelee` | `SetLabels(labels map[string]string) error` | 108 | | `labeler.GenericLabelee` | `SetLabels(labels map[string]string, tag string) error` | 109 | | `struct` with a container field | A `struct` with a field marked as being the container using `Options.ContainerToken` on any level of `v` or a field with the name matching `Options.ContainerField` that is any `type` above or an accessible `map[string]string`. (See [Options](#options)). | 110 | 111 | While marshaling, labeler will prioritize field values over those stored in your label 112 | container. This means that values in the `map[string]string` will be overridden 113 | if there is a key with a matching tag. 114 | 115 | labeler ignores the case of keys by default, but this is configurable. (See [Options](#options)) 116 | 117 | ## Input (Unmarshal) 118 | 119 | For `Unmarshal`, you also need to pass `input interface{}` which provides a means of 120 | accessing the labels `map[string]string`. 121 | 122 | `input` must satisfy one of the following: 123 | 124 | | Interface / Type | Signature | Example | 125 | | :--------------------------- | :---------------------------------------- | ---------------------------------------------------------- | 126 | | `labeler.Labeled` | `GetLabels() map[string]string` | [example](#basic-example-with-accessor-mutator-for-labels) | 127 | | `labeler.GenericallyLabeled` | `GetLabels(tag string) map[string]string` | [example](#example-using-multiple-tags) | 128 | | `map[string]string` | Any type derived from `map[string]string` | [example](#example-using-a-container-tag) | 129 | 130 | ## Labeler Instance 131 | 132 | If you need to change any Option, consider creating an instance of labeler as there will be a bit 133 | of extra pre-processing for each call to `Marshal` and `Unmarshal` otherwise. 134 | 135 | ```go 136 | environ := labeler.NewLabeler(OptTag("env")) 137 | // then use environ.Unmarshal(in, v) and environ.Marshal(v) 138 | _ = environ 139 | ``` 140 | 141 | ## Examples 142 | 143 | ### Basic example with accessor / mutator for labels 144 | 145 | ```go 146 | package main 147 | 148 | import ( 149 | "fmt" 150 | 151 | "github.com/chanced/labeler" 152 | ) 153 | 154 | type ExampleInput struct { 155 | Labels map[string]string 156 | } 157 | 158 | func (e ExampleInput) GetLabels() map[string]string { 159 | return e.Labels 160 | } 161 | 162 | type NestedExample struct { 163 | Field string `label:"nested_field"` 164 | } 165 | 166 | type Example struct { 167 | Name string `label:"name"` 168 | Duration time.Duration `label:"duration"` 169 | Time time.Time `label:"time, format: 01/02/2006 03:04PM"` 170 | Dedupe string `label:"dedupe, discard"` 171 | CaSe string `label:"CaSe, casesensitive"` 172 | FloatWithFormat float64 `label:"FloatWithFormat, format:b"` 173 | Float64 float64 `label:"float64"` 174 | Float32 float32 `label:"float32"` 175 | Int int `label:"int"` 176 | Int64 int64 `label:"int64"` 177 | Int32 int32 `label:"int32"` 178 | Int16 int16 `label:"int16"` 179 | Int8 int8 `label:"int8"` 180 | Bool bool `label:"bool"` 181 | Uint uint `label:"uint"` 182 | Uint64 uint64 `label:"uint64"` 183 | Uint32 uint32 `label:"uint32"` 184 | Uint16 uint16 `label:"uint16"` 185 | Uint8 uint8 `label:"uint8"` 186 | StrSlice []string `label:"str_slice"` 187 | IntArray [8]int `label:"int_array"` 188 | Nested NestedExample 189 | Labels map[string]string // SetLabels is used instead of the container 190 | } 191 | func (e *Example) SetLabels(l map[string]string) { 192 | e.Labels = l 193 | } 194 | func (e *Example) GetLabels() map[string]string { 195 | return e.Labels 196 | } 197 | 198 | func main() { 199 | labels := map[string]string{ 200 | "name": "Bart", 201 | "imp": "important field", 202 | "enum": "ValueB", 203 | "int": "123456789", 204 | "int64": "1234567890", 205 | "int32": "12345", 206 | "int16": "123", 207 | "int8": "1", 208 | "bool": "true", 209 | "duration": "1s", 210 | "float64": "1.1234567890", 211 | "float32": "1.123", 212 | "time": "09/26/2020 10:10PM", 213 | "uint": "1234", 214 | "uint64": "1234567890", 215 | "uint32": "1234567", 216 | "uint16": "123", 217 | "uint8": "1", 218 | "FloatWithFormat": "123.234823484", 219 | "dedupe": "Will be removed from the Labels after field value is set", 220 | "case": "value should not be set due to not matching case", 221 | "nested_field": "nested value", 222 | "int_array": "0,1,2,3,4,5,6,7" 223 | "str_slice": "red,blue,green" 224 | } 225 | 226 | input := ExampleInput { 227 | Labels: labels, 228 | } 229 | v := &Example{} 230 | 231 | err := labeler.Unmarshal(input, v) 232 | 233 | if err != nil { 234 | var pErr *labeler.ParsingError 235 | select { 236 | case errors.As(err, &pErr): 237 | for _, fieldErr := range pErr.Errors { 238 | // fieldErr has the field's Name (string) and Tag (labeler.Tag) 239 | // as well as Err, the underlying Error which unwraps 240 | } 241 | case errors.Is(err, ErrInvalidInput): 242 | // bad input 243 | } 244 | } 245 | 246 | l, err := labeler.Unmarshal(v) 247 | 248 | if err != nil { 249 | //handle err 250 | } 251 | 252 | fmt.Println(l) // map[string]string 253 | } 254 | ``` 255 | 256 | ### Example using a container tag 257 | 258 | If you don't want to use accessors and mutators on your input and value, you can opt instead for a container tag that is of `map[string]string` or implements the appropriate `interface` to retrieve and set labels. You can also pass in a `map[string]string` directly as your input for Unmarshal. 259 | 260 | The container token is configurable with the `ContainerToken` option. See [Options](#options) for more info. 261 | 262 | ```go 263 | package main 264 | 265 | import ( 266 | "fmt" 267 | "github.com/chanced/labeler" 268 | ) 269 | 270 | type Example2 struct { 271 | Name string `label:"name"` 272 | Defaulted string `label:"defaulted, default: my default value"` // spaces are trimmed 273 | Labels map[string]string `label:"*"` 274 | } 275 | 276 | func main() { 277 | l := map[string]string { name: "Homer" } 278 | 279 | v := &Example2{} 280 | 281 | err := labeler.Unmarshal(l, v) 282 | if err != nil { 283 | // handle err 284 | _ = err 285 | } 286 | fmt.Println("Name is: ", v.Name) 287 | fmt.Println(len(v.Labels), " Labels") 288 | labels, err := labeler.Marshal(v, l) 289 | if err != nil { 290 | _ = err 291 | } 292 | 293 | } 294 | ``` 295 | 296 | ### Example with an enum 297 | 298 | The only important bit is that `Color` implements `String() string` and 299 | `FromString(s string) error`. `Color` could have also implemented 300 | `UnmarshalText(text []byte) error` and `MarshalText() (text []byte, err error)`. 301 | 302 | ```go 303 | package main 304 | 305 | type Color int 306 | 307 | const ( 308 | ColorUnknown Color = iota 309 | ColorRed 310 | ColorBlue 311 | ) 312 | 313 | type Example3 struct { 314 | Color Color `label:"color"` 315 | Labels map[string]string `label:"*"` 316 | } 317 | 318 | var colorMapToStr map[Color]string = map[Color]string{ 319 | ColorUnknown: "Unknown", 320 | ColorBlue: "Blue", 321 | ColorRed: "Red", 322 | } 323 | 324 | 325 | func getColorMapFromStr() map[string]Color { 326 | m := make(map[string]Color) 327 | for k, v := range colorMapToStr { 328 | m[v] = k 329 | } 330 | return m 331 | } 332 | 333 | var colormMapFromStr map[string]Color = getColorMapFromStr() 334 | 335 | func (c Color) String() string { 336 | return colorMapToStr[my] 337 | } 338 | 339 | func (c *Color) FromString(s string) error { 340 | if v, ok := colorMapFromStr[s]; ok { 341 | *my = v 342 | return nil 343 | } 344 | return errors.New("Invalid value") 345 | } 346 | 347 | type Example3 struct { 348 | } 349 | 350 | ``` 351 | 352 | ### Example using multiple tags 353 | 354 | Say you have multiple sources of labels and you want to unmarshal them into the same `struct`. This is achievable by setting the option `Tag`! See [Options](#options) for more info. 355 | 356 | ```go 357 | import ( 358 | 359 | "github.com/chanced/labeler" 360 | ) 361 | 362 | type Example4 struct { 363 | Name string `property:"name"` 364 | Color string `attribute:"color"` 365 | Characteristics map[string]string 366 | Attributes map[string]string 367 | } 368 | 369 | func (e *Example4) GetLabels(t string){ 370 | switch t { 371 | case "property": 372 | return e.Characteristics 373 | case "attribute": 374 | return e.Attributes 375 | } 376 | } 377 | func (e *Example4) SetLabels(l map[string]string, t string) error{ 378 | switch t { 379 | case "property": 380 | e.Characteristics = l 381 | case "attribute": 382 | e.Attributes = l 383 | } 384 | } 385 | 386 | func main() { 387 | properties := map[string]string { name: "Homer" } 388 | attributes := map[string]string { color: "Yellow" } 389 | v := &Example4{} 390 | 391 | err := labeler.Unmarshal(v, properties, OptTag("property")) 392 | if err != nil { 393 | _ = err 394 | } 395 | err := labeler.Unmarshal(v, attributes, OptTag("attribute")) 396 | if err != nil { 397 | _ = err 398 | } 399 | 400 | } 401 | ``` 402 | 403 | ## Options 404 | 405 | ### Settings 406 | 407 | | Option | Default | Details | Option `func` | 408 | | :--------------- | :-------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------- | 409 | | `Tag` | `"label"` | `Tag` is the name of the tag to lookup. This is especially handy if you have multiple sources of labels | `OptTag(t string)` | 410 | | `Separator` | `","` | Seperates the tag attributes. Configurable incase you have a tag that contains commas. | `OptSeparator(v string)` | 411 | | `Split` | `","` | String used to split and join arrays and slices | `OptSplit(v string)` | 412 | | `ContainerField` | `""` | `ContainerField` determines the field to set and retrieve the labels in the form of `map[string]string`. If `ContainerField` is set, labeler will assume that `GetLabels` and `SetLabels` should not be utilized. To set the `ContainerField` of a nested field, use dot notation (`Root.Labels`).
`ContainerField` is not required if `input` implements the appropriate `interface` to retrieve and set labels. | `OptContainerField(s string)` | 413 | | `ContainerToken` | `"*"` | Used in place of the `ContainerField` option, indicating the container field via tag instead. It must derive from `map[string]string`. This option is only required if you do not wish to implement mutator/accessor interfaces. This can also be used to set some options such as `TimeFormat`, `FloatFormat`, `ComplexFormat`, `CaseSensitive`, `IntBase`, `UintBase` using the appropriate tokens. | `OptContainerToken(v string)` | 414 | | `AssignmentStr` | `":"` | Used to assign values. This is in the event that a default value needs to contain `":"` | `OptAssignmentStr(v string)` | 415 | | `KeepLabels` | `true` | Indicates whether or not labels that have been assigned to values are kept in the labels `map[string]string` when unmarshaling. | `OptKeepLabels()` `OptDiscardLabels()` | 416 | | `IgnoreCase` | `true` | If `true`, label keys are matched regardless of case. Setting this to `false` makes all keys case sensitive. This can be overridden at the field level. | `OptCaseSensitive()` | 417 | | `OmitEmpty` | `true` | Determines whether or not to set zero-value when marshaling and unmarshaling. | `OptOmitEmpty()` `OptIncludeEmpty()` | 418 | | `TimeFormat` | `""` | Default format / layout to use when formatting `time.Time`. Field level formats can be provided with either `format` (configurable) or `timeformat` (configurable) | `OptTimeFormat(v string)` | 419 | | `IntBase` | `10` | default base while parsing `int`, `int64`, `int32`, `int16`, `int8` | `OptIntBase(b int)` | 420 | | `UintBase` | `10` | default base while parsing `uint`, `uint64`, `uint32`, `uint16`, `uint8` | `OptUintBase(b int)` | 421 | | `ComplexFormat` | `'f'` | Default format to use when formatting `complex` values. | `OptComplexFormat(f byte)` | 422 | | `FloatFormat` | `'f'` | Default format to use when formatting `float` values. | `OptFloatFormat(f byte)` | 423 | 424 | ### Tokens 425 | 426 | | Option | Default | Details | Option `func` | 427 | | :------------------- | :---------------: | :------------------------------------------------------------------------------------------------------------------------------------------------ | :-------------------------------- | 428 | | `KeepToken` | `"keep"` | Token used to set `KeepLabels` to `true` | `OptKeepToken(v string)` | 429 | | `DiscardToken` | `"discard"` | Token used to set `KeepLabels` to `false` | `OptDiscardToken(v string)` | 430 | | `DefaultToken` | `"default"` | Token to provide a default value if one is not set. | `OptDefaultToken(v string)` | 431 | | `SplitToken` | `"split"` | Token used to set `Split` to `v` | `OptSplitToken(v string)` | 432 | | `CaseSensitiveToken` | `"casesensitive"` | Token used to set `IgnoreCase` to `false` | `OptCaseSensitiveToken(v string)` | 433 | | `IgnoreCaseToken` | `"ignorecase"` | Token used to determine whether or not to ignore case of the field's (or all fields if on container) key | `OptIgnoreCaseToken(v string)` | 434 | | `OmitEmptyToken` | `"omitempty"` | Token used to determine whether or not to assign empty / zero-value labels | `OptOmitEmptyToken(v string)` | 435 | | `IncludeEmptyToken` | `"includeempty"` | Token used to determine whether or not to assign empty / zero-value labels | `OptIncludeEmptyToken(v string)` | 436 | | `FormatToken` | `"format"` | Token to set the field-level formatting for `time` and `float`. | `OptFormatToken(v string)` | 437 | | `TimeFormatToken` | `"timeformat"` | Token used to set `TimeFormat`. `FormatToken` can be used on non-container fields instead. | `OptTimeFormatToken(v string)` | 438 | | `FloatFormatToken` | `"floatformat"` | Token used to set `FloatFormat`. `FormatToken` can be used on non-container fields instead. | `OptFloatFormatToken(v string)` | 439 | | `ComplexFormatToken` | `"complexformat"` | Token used to set `ComplexFormat`. `FormatToken` can be used on non-container fields instead. | `OptComplexFormatToken(v string)` | 440 | | `BaseToken` | `"base"` | sets the token for parsing base of `int`, `int64`, `int32`, `int16`, `int8`, `uint`, `uint64`, `uint32`, `uint16`, and `uint8` at the field level | `OptBaseToken(v string)` | 441 | | `IntBaseToken` | `"intbase"` | sets the token for parsing base of `int`, `int64`, `int32`, `int16`, `int8`, at the container or field level | `OptIntBaseToken(v string)` | 442 | | `UintBaseToken` | `"uintbase"` | sets the token for parsing base of `uint`, `uint64`, `uint32`, `uint16`, `uint8`, at the container or field level | `OptUintBaseToken(v string)` | 443 | 444 | ## Notes 445 | 446 | ### Prior Art 447 | 448 | - [go-env](https://github.com/Netflix/go-env) by Netflix. This is the only package that I looked at that does something similar. It was a huge help in getting started. 449 | 450 | If you run into any issues or have any questions, please do submit a ticket. 451 | 452 | ## License 453 | 454 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 455 | 456 | Do with it as you wish. 457 | -------------------------------------------------------------------------------- /errors.go: -------------------------------------------------------------------------------- 1 | package labeler 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "strings" 7 | ) 8 | 9 | var ( 10 | 11 | // Primary errors 12 | 13 | // ErrInvalidValue is returned when the value passed in does not satisfy the appropriate interfaces whilst also lacking a container field (configurable or taggable). v must be a non-nil struct 14 | ErrInvalidValue = errors.New("value must be a pointer to a struct or implement the appropriate interfaces") 15 | 16 | // ErrInvalidInput is returned when the input is not a non-nil pointer to a type implementing Labeled, which is any type that has a GetLabels method that returns a map[string]string, or a map[string]string 17 | ErrInvalidInput = errors.New("input must either be a non-nil pointer to a struct implementing Labeled or accessible as a map[string]string") 18 | 19 | // ErrParsing returned when there are errors parsing the value. See Errors for specific FieldErrors 20 | ErrParsing = errors.New("error(s) occurred while parsing") 21 | 22 | // ErrInvalidOption occurs when a required option or options is not assigned 23 | ErrInvalidOption = errors.New("invalid option") 24 | 25 | // ErrMultipleContainers is returned when there are more than one tag with "*" 26 | ErrMultipleContainers = errors.New("only one container field is allowed per tag") 27 | 28 | // ErrMissingContainer is returned when v does not have a SetLabels method and a container field has not been specified 29 | ErrMissingContainer = errors.New("v must have a SetLabels method or a container field for labels must be specified") 30 | 31 | // Field errors 32 | 33 | // ErrUnexportedField occurs when a field is marked with tag "label" (or Options.Tag) and not exported. 34 | ErrUnexportedField = errors.New("field must be exported") 35 | 36 | // ErrMalformedTag returned when a tag is empty / malformed 37 | ErrMalformedTag = errors.New("the label tag is malformed") 38 | 39 | // ErrInvalidFloatFormat occurs when either Options.FloatFormat or the format token is set to something other than 'b', 'e', 'E', 'f', 'g', 'G', 'x', or 'X' 40 | ErrInvalidFloatFormat = errors.New("invalid float format, options are: 'b', 'e', 'E', 'f', 'g', 'G', 'x', and 'X'") 41 | 42 | // ErrUnsupportedType is returned when a tag exists on a type that does not implement 43 | // Stringer/Stringee, UnmarsalText/MarshalText, or is not one of the following types: 44 | // string, bool, int, int64, int32, int16, int8, uint, uint64, uint32, uint16, uint8. 45 | // float64, float32, complex128, complex64 46 | ErrUnsupportedType = errors.New("unsupported type") 47 | 48 | //ErrMissingFormat is returned when a field requires formatting (time.Time for now) 49 | // but has not been set via the tag or Options (TimeFormat) 50 | ErrMissingFormat = errors.New("format is required for this field") 51 | 52 | // ErrSplitEmpty is returned when the split string is empty 53 | ErrSplitEmpty = errors.New("split can not be empty") 54 | // // ErrLabelRequired occurs when a label is marked as required but not available. 55 | // ErrLabelRequired = errors.New("value for this field is required") 56 | 57 | ) 58 | 59 | // FieldError is returned when there is an error parsing a field's tag due to 60 | // it being malformed or inaccessible. 61 | type FieldError struct { 62 | Field string 63 | Tag Tag 64 | Err error 65 | } 66 | 67 | func (err *FieldError) Error() string { 68 | return fmt.Sprintf("error parsing %s: %v", err.Field, err.Err) 69 | } 70 | 71 | func (err *FieldError) Unwrap() error { 72 | return err.Err 73 | } 74 | 75 | // NewFieldError creates a new FieldError 76 | func NewFieldError(fieldName string, err error) *FieldError { 77 | return &FieldError{ 78 | Field: fieldName, 79 | Err: err, 80 | } 81 | 82 | } 83 | 84 | // NewFieldErrorWithTag creates a new FieldError 85 | func NewFieldErrorWithTag(field string, t *Tag, err error) *FieldError { 86 | fe := &FieldError{ 87 | Field: field, 88 | Err: err, 89 | } 90 | if t != nil { 91 | fe.Tag = *t 92 | } 93 | return fe 94 | } 95 | 96 | func newFieldError(f *field, err error) *FieldError { 97 | return NewFieldErrorWithTag(f.name, f.tag, err) 98 | } 99 | 100 | // ParsingError is returned when there are 1 or more errors parsing a value. Check Errors for individual FieldErrors. 101 | type ParsingError struct { 102 | Errors []*FieldError 103 | } 104 | 105 | // NewParsingError returns a new ParsingError containing a slice of errors 106 | func NewParsingError(errs []*FieldError) *ParsingError { 107 | return &ParsingError{ 108 | Errors: errs, 109 | } 110 | } 111 | 112 | func (err *ParsingError) getFieldErrors() (int, string) { 113 | fields := []string{} 114 | count := len(err.Errors) 115 | for _, e := range err.Errors { 116 | fields = append(fields, e.Field) 117 | } 118 | return count, strings.Join(fields, ", ") 119 | } 120 | 121 | func (err *ParsingError) Unwrap() error { 122 | count, fields := err.getFieldErrors() 123 | return fmt.Errorf("%d %w (%s)", count, ErrParsing, fields) 124 | } 125 | 126 | func (err *ParsingError) Error() string { 127 | count, fields := err.getFieldErrors() 128 | msg := fmt.Sprintf("%d %v (%s)", count, ErrParsing, fields) 129 | for _, ferr := range err.Errors { 130 | msg = msg + fmt.Sprintf("\n\t%v", ferr) 131 | } 132 | return msg 133 | } 134 | 135 | // OptionError occurs when there is an in issue with an option. 136 | type OptionError struct { 137 | Option string 138 | Value string 139 | Msg string 140 | } 141 | 142 | // NewOptionError creates a new OptionError 143 | func NewOptionError(option, msg string) *OptionError { 144 | return &OptionError{ 145 | Option: option, 146 | Msg: msg, 147 | } 148 | } 149 | func (err *OptionError) Error() string { 150 | return fmt.Sprintf("%v %s: %v", ErrInvalidOption, err.Option, err.Msg) 151 | } 152 | func (err *OptionError) Unwrap() error { 153 | return fmt.Errorf("%w %s: %v", ErrInvalidOption, err.Option, err.Msg) 154 | 155 | } 156 | -------------------------------------------------------------------------------- /field.go: -------------------------------------------------------------------------------- 1 | package labeler 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "reflect" 7 | "strconv" 8 | "time" 9 | ) 10 | 11 | type field struct { 12 | meta 13 | tag *Tag 14 | parent reflected 15 | name string 16 | path string 17 | key string 18 | wasSet bool 19 | Keep bool 20 | isTagged bool 21 | isContainer bool 22 | } 23 | 24 | func newField(parent reflected, i int, o Options) (*field, error) { 25 | sf, ok := parent.StructField(i) 26 | if !ok { 27 | panic(errors.New("can not access field")) 28 | } 29 | rv, ok := parent.ValueField(i) 30 | if !ok { 31 | panic(errors.New("can not access field")) 32 | } 33 | 34 | fieldName := sf.Name 35 | f := &field{ 36 | name: fieldName, 37 | parent: parent, 38 | } 39 | f.path = f.Path() 40 | tag, err := f.parseTag(sf, o) 41 | 42 | if err != nil { 43 | return f, f.err(err) 44 | } 45 | f.tag = tag 46 | if tag != nil { 47 | f.key = tag.Key 48 | } 49 | 50 | f.meta = newMeta(rv) 51 | 52 | if !f.canAddr && f.isTagged { 53 | return f, f.err(ErrUnexportedField) 54 | } 55 | 56 | if f.isTagged && !f.canSet { 57 | return f, f.err(ErrUnexportedField) 58 | } 59 | 60 | if f.isTagged || f.IsContainer(o) { 61 | 62 | if f.kind == reflect.Map && f.value.IsNil() { 63 | f.value = reflect.New(f.typ).Elem() 64 | } 65 | 66 | f.unmarshal = getUnmarshal(f, o) 67 | f.marshal = getMarshal(f, o) 68 | if f.unmarshal == nil { 69 | 70 | return f, f.err(ErrUnsupportedType) 71 | } 72 | if f.marshal == nil { 73 | return f, f.err(ErrUnsupportedType) 74 | } 75 | } 76 | 77 | return f, nil 78 | } 79 | 80 | func (f *field) Unmarshal(kvs *keyValues, o Options) error { 81 | if f.unmarshal == nil { 82 | // this shouldn't happen. just being safe. 83 | return f.err(ErrUnsupportedType) 84 | } 85 | return f.unmarshal(f, kvs, o) 86 | } 87 | func (f *field) Marshal(kvs *keyValues, o Options) error { 88 | if f.marshal == nil { 89 | // this shouldn't happen. just being safe. 90 | return f.err(ErrUnsupportedType) 91 | } 92 | return f.marshal(f, kvs, o) 93 | } 94 | 95 | func (f *field) HasDefault(o Options) bool { 96 | return f.tag != nil && f.tag.DefaultIsSet 97 | } 98 | 99 | func (f *field) ignoreCase(o Options) bool { 100 | if f.tag.IgnoreCaseIsSet { 101 | return f.tag.IgnoreCase 102 | } 103 | return o.IgnoreCase 104 | } 105 | 106 | func (f *field) parseTag(sf reflect.StructField, o Options) (*Tag, error) { 107 | tagstr, isTagged := sf.Tag.Lookup(o.Tag) 108 | f.isTagged = isTagged 109 | if !isTagged { 110 | return nil, nil 111 | } 112 | return newTag(tagstr, o) 113 | } 114 | 115 | func (f *field) IsContainer(o Options) bool { 116 | switch { 117 | case o.ContainerField != "" && o.ContainerField == f.path: 118 | return true 119 | case f.tag != nil && f.tag.IsContainer: 120 | return true 121 | default: 122 | return false 123 | } 124 | } 125 | 126 | func (f *field) Path() string { 127 | if f.path != "" { 128 | return f.path 129 | } 130 | if f.parent.Path() != "" { 131 | return fmt.Sprintf("%s.%s", f.parent.Path(), f.name) 132 | } 133 | return f.name 134 | } 135 | 136 | func (f *field) err(err error) *FieldError { 137 | if err != nil { 138 | return newFieldError(f, err) 139 | } 140 | return nil 141 | } 142 | 143 | func (f *field) split(o Options) string { 144 | if s, ok := f.tag.GetSplit(); ok { 145 | return s 146 | } 147 | return o.Split 148 | } 149 | 150 | func (f *field) intBase(o Options) int { 151 | if base, ok := f.tag.GetIntBase(); ok { 152 | return base 153 | } 154 | return o.IntBase 155 | } 156 | func (f *field) uintBase(o Options) int { 157 | if base, ok := f.tag.GetUintBase(); ok { 158 | return base 159 | } 160 | return o.UintBase 161 | } 162 | 163 | func (f *field) floatFormat(o Options) byte { 164 | if format, ok := f.tag.GetFloatFormat(); ok { 165 | return format 166 | } 167 | return o.FloatFormat 168 | } 169 | 170 | func (f *field) complexFormat(o Options) byte { 171 | if format, ok := f.tag.GetComplexFormat(); ok { 172 | return format 173 | } 174 | return o.ComplexFormat 175 | } 176 | 177 | func (f *field) timeFormat(o Options) string { 178 | if format, ok := f.tag.GetTimeFormat(); ok { 179 | return format 180 | } 181 | return o.TimeFormat 182 | } 183 | 184 | func (f *field) formatInt(o Options) (string, error) { 185 | switch f.kind { 186 | case reflect.Int, reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8: 187 | v := f.value.Int() 188 | return strconv.FormatInt(v, f.intBase(o)), nil 189 | default: 190 | return "", nil 191 | } 192 | } 193 | 194 | func (f *field) setInt(s string, bits int, o Options) error { 195 | v, err := strconv.ParseInt(s, f.intBase(o), bits) 196 | if err != nil { 197 | return f.err(err) 198 | } 199 | f.value.SetInt(v) 200 | return nil 201 | } 202 | 203 | func (f *field) formatString(o Options) (string, error) { 204 | return f.value.String(), nil 205 | } 206 | 207 | func (f *field) setString(s string, o Options) error { 208 | f.value.SetString(s) 209 | return nil 210 | } 211 | 212 | func (f *field) formatUint(o Options) (string, error) { 213 | switch f.kind { 214 | case reflect.Uint, reflect.Uint64, reflect.Uint32, reflect.Uint16, reflect.Uint8: 215 | v := f.value.Uint() 216 | return strconv.FormatUint(v, f.intBase(o)), nil 217 | default: 218 | return "", nil 219 | } 220 | } 221 | 222 | func (f *field) setUint(s string, bits int, o Options) error { 223 | v, err := strconv.ParseUint(s, f.uintBase(o), bits) 224 | if err != nil { 225 | return f.err(err) 226 | } 227 | f.value.SetUint(v) 228 | return nil 229 | } 230 | 231 | func (f *field) formatComplex(o Options) (string, error) { 232 | switch f.kind { 233 | case reflect.Complex128: 234 | v := f.value.Complex() 235 | return strconv.FormatComplex(v, f.complexFormat(o), -1, 128), nil 236 | case reflect.Complex64: 237 | v := f.value.Complex() 238 | return strconv.FormatComplex(v, f.complexFormat(o), -1, 64), nil 239 | default: 240 | return "", nil 241 | } 242 | } 243 | 244 | func (f *field) setComplex(s string, bits int, o Options) error { 245 | v, err := strconv.ParseComplex(s, bits) 246 | if err != nil { 247 | return f.err(err) 248 | } 249 | f.value.SetComplex(v) 250 | return nil 251 | } 252 | 253 | func (f *field) formatFloat(o Options) (string, error) { 254 | switch f.kind { 255 | case reflect.Float64: 256 | v := f.value.Float() 257 | return strconv.FormatFloat(v, f.floatFormat(o), -1, 64), nil 258 | case reflect.Float32: 259 | v := f.value.Float() 260 | return strconv.FormatFloat(v, f.floatFormat(o), -1, 32), nil 261 | default: 262 | return "", nil 263 | } 264 | } 265 | 266 | func (f *field) setFloat(s string, bits int, o Options) error { 267 | v, err := strconv.ParseFloat(s, bits) 268 | if err != nil { 269 | return f.err(err) 270 | } 271 | f.value.SetFloat(v) 272 | return nil 273 | } 274 | 275 | func (f *field) formatBool(o Options) (string, error) { 276 | if f.kind == reflect.Bool { 277 | v := f.value.Bool() 278 | return strconv.FormatBool(v), nil 279 | } 280 | return "", nil 281 | } 282 | 283 | func (f *field) setBool(s string, o Options) error { 284 | v, err := strconv.ParseBool(s) 285 | if err != nil { 286 | return f.err(err) 287 | } 288 | f.value.SetBool(v) 289 | return nil 290 | } 291 | 292 | func (f *field) formatTime(o Options) (string, error) { 293 | if v, ok := f.Interface().(*time.Time); ok { 294 | return v.Format(f.timeFormat(o)), nil 295 | } 296 | return "", nil 297 | } 298 | 299 | func (f *field) setTime(s string, o Options) error { 300 | if !timeType.AssignableTo(f.Type()) { 301 | return f.err(errors.New("Can not assign time.Time to " + f.name)) 302 | } 303 | v, err := time.Parse(f.timeFormat(o), s) 304 | if err != nil { 305 | return f.err(err) 306 | } 307 | rv := reflect.ValueOf(v) 308 | f.value.Set(rv) 309 | return nil 310 | } 311 | 312 | func (f *field) formatDuration(o Options) (string, error) { 313 | switch v := f.Interface().(type) { 314 | case *time.Duration: 315 | return v.String(), nil 316 | default: 317 | return "", nil 318 | } 319 | 320 | } 321 | 322 | func (f *field) setDuration(s string, o Options) error { 323 | v, err := time.ParseDuration(s) 324 | if err != nil { 325 | return f.err(err) 326 | } 327 | rv := reflect.ValueOf(v) 328 | f.value.Set(rv) 329 | return nil 330 | } 331 | 332 | func (f *field) setMap(v map[string]string, o Options) error { 333 | if f.kind != reflect.Map { 334 | return f.err(errors.New("invalid type")) // this shouldn't happen 335 | } 336 | f.value.Set(reflect.ValueOf(v)) 337 | return nil 338 | } 339 | 340 | func (f *field) ShouldKeep(o Options) bool { 341 | if f.tag.KeepIsSet { 342 | return f.tag.Keep 343 | } 344 | return o.KeepLabels 345 | } 346 | 347 | func (f *field) OmitEmpty(o Options) bool { 348 | if f.tag != nil { 349 | if f.tag.OmitEmptyIsSet { 350 | return true 351 | } 352 | if f.tag.IncludeEmptyIsSet { 353 | return false 354 | } 355 | } 356 | return o.OmitEmpty 357 | } 358 | 359 | func (f *field) ShouldDiscard(o Options) bool { 360 | return !f.ShouldKeep(o) 361 | } 362 | 363 | func (f *field) Default(o Options) string { 364 | if f.tag.DefaultIsSet { 365 | return f.tag.Default 366 | } 367 | return o.Default 368 | } 369 | 370 | func (f *field) Save() { 371 | f.save() 372 | f.parent.Save() 373 | } 374 | 375 | func (f *field) Topic() topic { 376 | return fieldTopic 377 | } 378 | -------------------------------------------------------------------------------- /fieldset.go: -------------------------------------------------------------------------------- 1 | package labeler 2 | 3 | import "errors" 4 | 5 | type fieldset struct { 6 | tagged []*field 7 | container *field 8 | } 9 | 10 | func newFieldset() fieldset { 11 | fs := fieldset{ 12 | tagged: []*field{}, 13 | } 14 | return fs 15 | } 16 | 17 | func (fs *fieldset) setContainer(f *field, o Options) error { 18 | if fs.container != nil { 19 | if fs.container.Path() != f.Path() { 20 | return ErrMultipleContainers 21 | } 22 | return nil 23 | } 24 | fs.container = f 25 | return nil 26 | } 27 | 28 | func (fs *fieldset) processField(f *field, o Options) error { 29 | if f == nil { 30 | return errors.New("field was nil") 31 | } 32 | if f.IsContainer(o) { 33 | return fs.setContainer(f, o) 34 | } 35 | if f.isTagged { 36 | fs.tagged = append(fs.tagged, f) 37 | } 38 | return nil 39 | } 40 | 41 | func (fs *fieldset) containerTag() *Tag { 42 | if fs.container == nil { 43 | return nil 44 | } 45 | return fs.container.tag 46 | } 47 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/chanced/labeler 2 | 3 | go 1.13 4 | 5 | require github.com/stretchr/testify v1.6.1 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 2 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 4 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 5 | github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= 6 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 7 | github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= 8 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 9 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 10 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 11 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 12 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 13 | -------------------------------------------------------------------------------- /input.go: -------------------------------------------------------------------------------- 1 | package labeler 2 | 3 | import ( 4 | "errors" 5 | "reflect" 6 | ) 7 | 8 | type input struct { 9 | meta 10 | } 11 | 12 | func newInput(v interface{}, o Options) (input, error) { 13 | rv := reflect.ValueOf(v) 14 | in := input{ 15 | meta: newMeta(rv), 16 | } 17 | if in.kind == reflect.Map && in.value.IsNil() { 18 | in.value = reflect.New(in.typ).Elem() 19 | } 20 | 21 | in.marshal = getMarshal(&in, o) 22 | if in.marshal == nil { 23 | return in, ErrInvalidInput 24 | } 25 | return in, nil 26 | } 27 | 28 | func (in *input) Unmarshal(kvs *keyValues, o Options) error { 29 | return errors.New("cannot unmarshal input") 30 | } 31 | 32 | func (in *input) Marshal(kvs *keyValues, o Options) error { 33 | if in.marshal == nil { 34 | return ErrInvalidInput 35 | } 36 | return in.marshal(in, kvs, o) 37 | } 38 | 39 | func (in *input) IsContainer(o Options) bool { 40 | return false 41 | } 42 | 43 | func (in *input) Path() string { 44 | return "" 45 | } 46 | 47 | func (in *input) Topic() topic { 48 | return inputTopic 49 | } 50 | 51 | func (in *input) Save() {} 52 | -------------------------------------------------------------------------------- /interfaces.go: -------------------------------------------------------------------------------- 1 | package labeler 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "time" 7 | ) 8 | 9 | // Labelee is implemented by any type with a SetLabels method, which 10 | // accepts map[string]string and handles assignment of those values. 11 | type Labelee interface { 12 | SetLabels(labels map[string]string) 13 | } 14 | 15 | // StrictLabelee is implemented by types with a SetLabels method, which accepts 16 | // map[string]string and handles assignment of those values, returning error if 17 | // there was an issue assigning the value. 18 | type StrictLabelee interface { 19 | SetLabels(labels map[string]string) error 20 | } 21 | 22 | // GenericLabelee is implemented by any type with a SetLabels method, which 23 | // accepts map[string]string and handles assignment of those values. 24 | type GenericLabelee interface { 25 | SetLabels(labels map[string]string, tag string) error 26 | } 27 | 28 | // Labeled is implemented by types with a method GetLabels, which returns 29 | // a map[string]string of labels and values 30 | type Labeled interface { 31 | GetLabels() map[string]string 32 | } 33 | 34 | // GenericallyLabeled is implemented by types with a method GetLabels, which 35 | // accepts a string and returns a map[string]string of labels and values 36 | type GenericallyLabeled interface { 37 | GetLabels(t string) map[string]string 38 | } 39 | 40 | // Unmarshaler is implemented by any type that has the method UnmarshalLabels, 41 | // providing a means of unmarshaling map[string]string themselves. 42 | type Unmarshaler interface { 43 | UnmarshalLabels(v map[string]string) error 44 | } 45 | 46 | // UnmarshalerWithOpts is implemented by any type that has the method 47 | // UnmarshalLabels, providing a means of unmarshaling map[string]string 48 | // that also accepts Options. 49 | type UnmarshalerWithOpts interface { 50 | UnmarshalLabels(v map[string]string, opts Options) error 51 | } 52 | 53 | // Marshaler is implemented by types with the method MarsahlLabels, 54 | // thus being abel to marshal itself into map[string]string 55 | type Marshaler interface { 56 | MarshalLabels() (map[string]string, error) 57 | } 58 | 59 | // MarshalerWithOpts is implemented by types with the method MarsahlLabels, 60 | // thus being abel to marshal itself into map[string]string 61 | type MarshalerWithOpts interface { 62 | MarshalLabels(o Options) (map[string]string, error) 63 | } 64 | 65 | // Stringee is implemented by any value that has a FromString method, 66 | // which parses the “native” format for that value from a string and 67 | // returns a bool value to indicate success (true) or failure (false) 68 | // of parsing. 69 | // Use StringeeStrict if returning an error is preferred. 70 | type Stringee interface { 71 | FromString(s string) error 72 | } 73 | 74 | // TextUnmarshaler is the interface implemented by an object that can unmarshal a textual representation of itself. 75 | // 76 | // UnmarshalText must be able to decode the form generated by MarshalText. UnmarshalText must copy the text if it wishes to retain the text after returning. 77 | type TextUnmarshaler interface { 78 | UnmarshalText(text []byte) error 79 | } 80 | 81 | // TextMarshaler is the interface implemented by an object that can marshal itself into a textual form. 82 | // 83 | // MarshalText encodes the receiver into UTF-8-encoded text and returns the result. 84 | type TextMarshaler interface { 85 | MarshalText() (text []byte, err error) 86 | } 87 | 88 | var labeleeType reflect.Type = reflect.TypeOf(new(Labelee)).Elem() 89 | var strictLabeleeType reflect.Type = reflect.TypeOf(new(StrictLabelee)).Elem() 90 | var genericLabeleeType reflect.Type = reflect.TypeOf(new(GenericLabelee)).Elem() 91 | var labeledType = reflect.TypeOf(new(Labeled)).Elem() 92 | var genericallyLabeledType = reflect.TypeOf(new(GenericallyLabeled)).Elem() 93 | var unmarshalerType = reflect.TypeOf(new(Unmarshaler)).Elem() 94 | var unmarshalerWithOptsType = reflect.TypeOf(new(UnmarshalerWithOpts)).Elem() 95 | var marshalerType = reflect.TypeOf(new(Marshaler)).Elem() 96 | var marshalerWithOptsType = reflect.TypeOf(new(MarshalerWithOpts)).Elem() 97 | var stringeeType = reflect.TypeOf(new(Stringee)).Elem() 98 | var textUnmarshalerType = reflect.TypeOf(new(TextUnmarshaler)).Elem() 99 | var textMarshalerType = reflect.TypeOf((new(TextMarshaler))).Elem() 100 | var stringerType = reflect.TypeOf(new(fmt.Stringer)).Elem() 101 | var stringType = reflect.TypeOf("") 102 | var mapType reflect.Type = reflect.MapOf(stringType, stringType) 103 | var timeType = reflect.TypeOf(time.Time{}) 104 | var durationType = func() reflect.Type { var d time.Duration; return reflect.TypeOf(d) }() 105 | -------------------------------------------------------------------------------- /keyvalue.go: -------------------------------------------------------------------------------- 1 | package labeler 2 | 3 | import "strings" 4 | 5 | type keyvalue struct { 6 | Key string 7 | Value string 8 | } 9 | 10 | type keyValues struct { 11 | lookup map[string]*keyvalue 12 | lcase map[string]*keyvalue 13 | m map[string]string 14 | } 15 | 16 | func newKeyValues() keyValues { 17 | kvs := keyValues{ 18 | lookup: make(map[string]*keyvalue), 19 | lcase: make(map[string]*keyvalue), 20 | m: make(map[string]string), 21 | } 22 | return kvs 23 | } 24 | 25 | func (kvs *keyValues) Get(key string, ignorecase bool) (keyvalue, bool) { 26 | var kv *keyvalue 27 | var ok bool 28 | if ignorecase { 29 | kv, ok = kvs.lcase[strings.ToLower(key)] 30 | } else { 31 | kv, ok = kvs.lookup[key] 32 | } 33 | if ok { 34 | return *kv, ok 35 | } 36 | return keyvalue{}, ok 37 | 38 | } 39 | 40 | func (kvs *keyValues) Set(key string, v string) { 41 | kv := &keyvalue{Key: key, Value: v} 42 | kvs.lookup[key] = kv 43 | kvs.lcase[strings.ToLower(key)] = kv 44 | kvs.m[key] = v 45 | } 46 | 47 | func (kvs *keyValues) Map() map[string]string { 48 | return kvs.m 49 | } 50 | 51 | func (kvs *keyValues) Delete(key string) { 52 | delete(kvs.m, key) 53 | delete(kvs.lookup, key) 54 | delete(kvs.lcase, strings.ToLower(key)) 55 | } 56 | 57 | func (kvs *keyValues) Add(m map[string]string) { 58 | if m == nil { 59 | return 60 | } 61 | for k, v := range m { 62 | kvs.Set(k, v) 63 | } 64 | } 65 | func (kvs *keyValues) AddSet(v keyValues) { 66 | kvs.Add(kvs.Map()) 67 | } 68 | -------------------------------------------------------------------------------- /labeler.go: -------------------------------------------------------------------------------- 1 | // Package labeler marshals and unmarshals map[string]string utilizing struct tags. 2 | package labeler 3 | 4 | // Labeler Marshals and Unmarshals map[string]string based on struct tags and options 5 | type Labeler struct { 6 | options Options 7 | } 8 | 9 | // Unmarshal parses labels and unmarshals them into v. See README.md for 10 | // available options for input and v. 11 | func Unmarshal(input interface{}, v interface{}, opts ...Option) error { 12 | lbl := NewLabeler(opts...) 13 | return lbl.Unmarshal(input, v) 14 | } 15 | 16 | // Marshal parses v, pulling the values from tagged fields (default: "label") as 17 | // well as any labels returned from GetLabels(), GetLabels(tag string), or the 18 | // value of the ContainerField, set either with a tag `labels:"*"` (note: both "*" 19 | // and labels are configurable). Tagged field values take precedent over these 20 | // values as they are just present to ensure that all labels are stored, regardless 21 | // of unmarshaling. 22 | func Marshal(v interface{}, opts ...Option) (l map[string]string, err error) { 23 | lbl := NewLabeler(opts...) 24 | return lbl.Marshal(v) 25 | } 26 | 27 | // NewLabeler returns a new Labeler instance based upon Options (if any) provided. 28 | func NewLabeler(opts ...Option) Labeler { 29 | o := newOptions(opts) 30 | lbl := Labeler{ 31 | options: o, 32 | } 33 | return lbl 34 | } 35 | 36 | // ValidateOptions checks the options provided 37 | func (lbl Labeler) ValidateOptions() error { 38 | return lbl.options.Validate() 39 | } 40 | 41 | //Unmarshal input into v using the Options provided to Labeler 42 | func (lbl *Labeler) Unmarshal(input interface{}, v interface{}) error { 43 | o := lbl.options 44 | sub, err := newSubject(v, o) 45 | if err != nil { 46 | return err 47 | } 48 | kvs := newKeyValues() 49 | in, err := newInput(input, o) 50 | if err != nil { 51 | return err 52 | } 53 | err = in.Marshal(&kvs, o) 54 | if err != nil { 55 | return err 56 | } 57 | return sub.Unmarshal(&kvs, o) 58 | } 59 | 60 | // Marshal v into map[string]string using the Options provided to Labeler 61 | func (lbl *Labeler) Marshal(v interface{}) (map[string]string, error) { 62 | o := lbl.options 63 | kvs := newKeyValues() 64 | sub, err := newSubject(v, o) 65 | if err != nil { 66 | return kvs.Map(), err 67 | } 68 | err = sub.Marshal(&kvs, o) 69 | return kvs.Map(), err 70 | } 71 | -------------------------------------------------------------------------------- /labeler_test.go: -------------------------------------------------------------------------------- 1 | package labeler 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "strings" 7 | "testing" 8 | "time" 9 | 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | type StructWithLabels struct { 14 | Labels map[string]string 15 | } 16 | 17 | func (s StructWithLabels) GetLabels() map[string]string { 18 | return s.Labels 19 | } 20 | 21 | type MyEnum int 22 | 23 | const ( 24 | EnumUnknown MyEnum = iota 25 | EnumValA 26 | EnumValB 27 | ) 28 | 29 | var myEnumMapToStr map[MyEnum]string = map[MyEnum]string{ 30 | EnumUnknown: "Unknown", 31 | EnumValA: "ValueA", 32 | EnumValB: "ValueB", 33 | } 34 | 35 | func getMyEnumMapFromStr() map[string]MyEnum { 36 | m := make(map[string]MyEnum) 37 | for k, v := range myEnumMapToStr { 38 | m[v] = k 39 | } 40 | return m 41 | } 42 | 43 | var myEnumMapFromStr map[string]MyEnum = getMyEnumMapFromStr() 44 | 45 | func (my *MyEnum) String() string { 46 | return myEnumMapToStr[*my] 47 | } 48 | 49 | var ErrExampleInvalidEnum = errors.New("invalid MyEnum Value") 50 | 51 | func (my *MyEnum) FromString(s string) error { 52 | if v, ok := myEnumMapFromStr[s]; ok { 53 | *my = v 54 | return nil 55 | } 56 | return ErrExampleInvalidEnum 57 | } 58 | 59 | type Example struct { 60 | Name string `label:"name"` 61 | Enum MyEnum `label:"enum"` 62 | Duration time.Duration `label:"duration"` 63 | Time time.Time `label:"time, format: 01/02/2006 03:04PM"` 64 | Time2 time.Time `label:"time2, timeformat: 01/02/2006 03:04PM"` 65 | Dedupe string `label:"dedupe, discard"` 66 | WithDefault string `label:"withdefault, default:defaultvalue"` 67 | CaSe string `label:"CaSe, casesensitive"` 68 | FloatWithFormat float64 `label:"floatWithFormat, format:b"` 69 | FloatWithFormat2 float64 `label:"floatWithFormat2, floatformat:b"` 70 | Complex128 complex128 `label:"complex128"` 71 | Complex64 complex64 `label:"complex64"` 72 | ComplexWithFormat complex64 `label:"complexWithFormat,format:b"` 73 | ComplexWithFormat2 complex64 `label:"complexWithFormat2,complexformat:b"` 74 | Float64 float64 `label:"float64"` 75 | Float32 float32 `label:"float32"` 76 | Int int `label:"int"` 77 | IntBinary int `label:"intbinary,base:2"` 78 | Int64 int64 `label:"int64"` 79 | Int32 int32 `label:"int32"` 80 | Int16 int16 `label:"int16"` 81 | Int8 int8 `label:"int8"` 82 | Bool bool `label:"bool"` 83 | Uint uint `label:"uint"` 84 | Uint64 uint64 `label:"uint64"` 85 | Uint32 uint32 `label:"uint32"` 86 | Uint16 uint16 `label:"uint16"` 87 | Uint8 uint8 `label:"uint8"` 88 | 89 | Labels map[string]string 90 | } 91 | 92 | func (e *Example) SetLabels(l map[string]string) { 93 | e.Labels = l 94 | } 95 | 96 | func (e *Example) GetLabels() map[string]string { 97 | return e.Labels 98 | } 99 | 100 | type ExampleWithEnum struct { 101 | Enum MyEnum `label:"enum"` 102 | Labels map[string]string `label:"*"` 103 | } 104 | 105 | func TestUnmarshalExample(t *testing.T) { 106 | labels := map[string]string{ 107 | "name": "Archer", 108 | "enum": "ValueB", 109 | "int": "123456789", 110 | "int64": "1234567890", 111 | "int32": "12345", 112 | "int16": "123", 113 | "int8": "1", 114 | "intbinary": "111", 115 | "bool": "true", 116 | "duration": "1s", 117 | "float64": "1.1234567890", 118 | "float32": "1.123", 119 | "complex64": "3+4i", 120 | "complex128": "3+4i", 121 | "time": "09/26/2020 10:10PM", 122 | "time2": "09/26/2020 10:10PM", 123 | "uint": "1234", 124 | "uint64": "1234567890", 125 | "uint32": "1234567", 126 | "uint16": "123", 127 | "uint8": "1", 128 | "floatWithFormat": "123.234823484", 129 | "floatWithFormat2": "123.234823484", 130 | "complexWithFormat": "123.234823484", 131 | "complexWithFormat2": "123.234823484", 132 | "dedupe": "Demonstrates that discard is removed from the Labels after field value is set", 133 | "case": "value should not be set due to not matching case", 134 | } 135 | 136 | input := StructWithLabels{ 137 | Labels: labels, 138 | } 139 | 140 | v := &Example{} 141 | err := Unmarshal(input, v) 142 | var pErr *ParsingError 143 | if errors.As(err, &pErr) { 144 | for _, e := range pErr.Errors { 145 | fmt.Println(e) 146 | } 147 | } 148 | assert.NoError(t, err, "Should not have thrown an error") 149 | 150 | assert.Equal(t, "Archer", v.Name, "Name should be set to \"Archer\"") 151 | assert.Equal(t, EnumValB, v.Enum, "Enum should be set to EnumValB") 152 | assert.Equal(t, true, v.Bool, "Bool should be set to true") 153 | assert.Equal(t, 123456789, v.Int, "Int should be set to 123456789") 154 | assert.Equal(t, int8(1), v.Int8, "Int8 should be set to 1") 155 | assert.Equal(t, int(7), v.IntBinary, "IntBinary should be set to 7") 156 | assert.Equal(t, int16(123), v.Int16, "Int16 should be set to 123") 157 | assert.Equal(t, int32(12345), v.Int32, "Int32 should be set to 12345") 158 | assert.Equal(t, int64(1234567890), v.Int64, "Int64 should be set to 1234567890") 159 | assert.Equal(t, float64(1.1234567890), v.Float64, "Float64 should be ste to 1.1234567890") 160 | assert.Equal(t, float32(1.123), v.Float32, "Float32 should be 1.123") 161 | assert.Equal(t, time.Second*1, v.Duration, "Duration should be 1 second") 162 | assert.Equal(t, uint(1234), v.Uint, "Unit should be set to 1234") 163 | assert.Equal(t, uint64(1234567890), v.Uint64, "Uint64 should be set to 1234567890") 164 | assert.Equal(t, uint32(1234567), v.Uint32, "Uinit32 should be set to 1234567") 165 | assert.Equal(t, uint16(123), v.Uint16, "Unit16 should be set to 123") 166 | assert.Equal(t, uint8(1), v.Uint8, "Uint8 should be set to 1") 167 | assert.Equal(t, "defaultvalue", v.WithDefault, "WithDefault should have been set to defaultvalue per tag") 168 | assert.Zero(t, v.CaSe) 169 | assert.Equal(t, "Demonstrates that discard is removed from the Labels after field value is set", v.Dedupe) 170 | assert.NotContains(t, v.GetLabels(), "dedupe") 171 | assert.Equal(t, time.Date(int(2020), time.September, int(26), int(22), int(10), int(0), int(0), time.UTC), v.Time) 172 | assert.Equal(t, time.Date(int(2020), time.September, int(26), int(22), int(10), int(0), int(0), time.UTC), v.Time2) 173 | fmt.Println(v) 174 | 175 | } 176 | 177 | func TestMarshalExample(t *testing.T) { 178 | labels := map[string]string{ 179 | "name": "Archer", 180 | "enum": "ValueB", 181 | "int": "123456789", 182 | "int64": "1234567890", 183 | "int32": "12345", 184 | "int16": "123", 185 | "int8": "1", 186 | "intbinary": "111", 187 | "bool": "true", 188 | "duration": "1s", 189 | "float64": "1.123456789", 190 | "float32": "1.123", 191 | "complex64": "(3+4i)", 192 | "complex128": "(3+4i)", 193 | "time": "09/26/2020 10:10PM", 194 | "time2": "09/26/2020 10:10PM", 195 | "uint": "1234", 196 | "uint64": "1234567890", 197 | "uint32": "12345", 198 | "uint16": "123", 199 | "uint8": "1", 200 | "floatWithFormat": "8671879767525176p-46", 201 | "floatWithFormat2": "8671879767525176p-46", 202 | "complexWithFormat": "(16152635p-17+0p-149i)", 203 | "complexWithFormat2": "(16152635p-17+0p-149i)", 204 | } 205 | v := &Example{ 206 | Name: "Archer", 207 | Bool: true, 208 | CaSe: "", 209 | Duration: 1 * time.Second, 210 | Enum: EnumValB, 211 | Complex128: 3 + 4i, 212 | Complex64: 3 + 4i, 213 | Float32: 1.123, 214 | Float64: 1.1234567890, 215 | Time: time.Date(int(2020), time.September, int(26), int(22), int(10), int(0), int(0), time.UTC), 216 | Time2: time.Date(int(2020), time.September, int(26), int(22), int(10), int(0), int(0), time.UTC), 217 | IntBinary: 7, 218 | Int: 123456789, 219 | Int64: 1234567890, 220 | Int32: 12345, 221 | Int16: 123, 222 | Int8: 1, 223 | Uint8: 1, 224 | Uint16: 123, 225 | Uint32: 12345, 226 | Uint64: 1234567890, 227 | Uint: 1234, 228 | FloatWithFormat: 123.234823484, 229 | FloatWithFormat2: 123.234823484, 230 | ComplexWithFormat: 123.234823484, 231 | ComplexWithFormat2: 123.234823484, 232 | } 233 | res, err := Marshal(v) 234 | assert.NoError(t, err) 235 | for key, value := range labels { 236 | fmt.Printf("%-20s %s%30s\n", key, res[key], value) 237 | } 238 | for key, value := range labels { 239 | assert.Contains(t, res, key, "marshaled results should contain ", key) 240 | v, ok := res[key] 241 | 242 | if ok { 243 | assert.Equal(t, v, value, fmt.Sprintf("%s should equal %v. got %s", key, value, v)) 244 | } 245 | 246 | } 247 | 248 | } 249 | 250 | func TestInputAsMap(t *testing.T) { 251 | v := &Example{} 252 | labels := map[string]string{ 253 | "name": "Archer", 254 | "imp": "important field", 255 | "enum": "ValueB", 256 | "int": "123456789", 257 | "int64": "1234567890", 258 | "int32": "12345", 259 | "int16": "123", 260 | "int8": "1", 261 | "intbinary": "111", 262 | "bool": "true", 263 | "duration": "1s", 264 | "float64": "1.1234567890", 265 | "float32": "1.123", 266 | "complex64": "(3+4i)", 267 | "complex128": "(3+4i)", 268 | "time": "09/26/2020 10:10PM", 269 | "time2": "09/26/2020 10:10PM", 270 | "uint": "1234", 271 | "uint64": "1234567890", 272 | "uint32": "1234567", 273 | "uint16": "123", 274 | "uint8": "1", 275 | "floatWithFormat": "123.234823484", 276 | "floatWithFormat2": "123.234823484", 277 | "complexWithFormat": "123.234823484", 278 | "complexWithFormat2": "123.234823484", 279 | "dedupe": "Demonstrates that discard is removed from the Labels after field value is set", 280 | "case": "value should not be set due to not matching case", 281 | } 282 | 283 | err := Unmarshal(labels, v) 284 | assert.NoError(t, err, "Should not have thrown an error") 285 | 286 | assert.Equal(t, "Archer", v.Name, "Name should be set to \"Archer\"") 287 | assert.Equal(t, EnumValB, v.Enum, "Enum should be set to EnumValB") 288 | 289 | } 290 | 291 | func TestEnum(t *testing.T) { 292 | labels := map[string]string{ 293 | "enum": "ValueB", 294 | } 295 | 296 | input := StructWithLabels{ 297 | Labels: labels, 298 | } 299 | 300 | v := &ExampleWithEnum{} 301 | 302 | err := Unmarshal(input, v) 303 | assert.NoError(t, err, "Should not have thrown an error") 304 | assert.Equal(t, EnumValB, v.Enum, "Enum should be set to EnumValB") 305 | 306 | } 307 | 308 | type InvalidDueToNonaddressableContainer struct { 309 | Name string `label:"name"` 310 | labels map[string]string `label:"*"` 311 | } 312 | 313 | func TestInvalidValueDueToUnaccessibleContainer(t *testing.T) { 314 | l := StructWithLabels{ 315 | Labels: map[string]string{}, 316 | } 317 | 318 | v := &InvalidDueToNonaddressableContainer{} 319 | err := Unmarshal(l, v) 320 | assert.Error(t, err) 321 | var pErr *ParsingError 322 | if errors.As(err, &pErr) { 323 | if len(pErr.Errors) == 0 { 324 | assert.Fail(t, "ParsingError.Errors should contain an ErrUnexportedField for labels") 325 | } else { 326 | if !errors.Is(pErr.Errors[0], ErrUnexportedField) { 327 | assert.Fail(t, "ParsingError.Errors should contain an ErrUnexportedField for labels") 328 | } 329 | } 330 | 331 | } else { 332 | assert.Fail(t, "err should be a parsing error with") 333 | } 334 | } 335 | 336 | type WithDiscard struct { 337 | Discarded string `label:"will_not_be_in_labels,discard"` 338 | Kept string `label:"will_be_in_labels"` 339 | Labels map[string]string 340 | } 341 | 342 | func (wd *WithDiscard) SetLabels(labels map[string]string) { 343 | wd.Labels = labels 344 | } 345 | 346 | func TestLabeleeWithDiscard(t *testing.T) { 347 | l := StructWithLabels{ 348 | Labels: map[string]string{ 349 | "will_not_be_in_labels": "discarded_value", 350 | "will_be_in_labels": "kept_value", 351 | "unassigned": "unassigned will be in labels", 352 | }, 353 | } 354 | 355 | v := &WithDiscard{} 356 | err := Unmarshal(l, v) 357 | assert.NoError(t, err) 358 | assert.Equal(t, "discarded_value", v.Discarded) 359 | assert.Equal(t, "kept_value", v.Kept) 360 | assert.NotContains(t, v.Labels, "will_not_be_in_labels") 361 | assert.Contains(t, v.Labels, "will_be_in_labels") 362 | assert.Contains(t, v.Labels, "unassigned") 363 | } 364 | 365 | type Nested struct { 366 | SubField string `label:"subfield"` 367 | } 368 | 369 | type WithNested struct { 370 | Nested Nested 371 | ParentField string `label:"parentfield"` 372 | Labels map[string]string `label:"*"` 373 | } 374 | 375 | func TestLabeleeWithNestedStruct(t *testing.T) { 376 | l := StructWithLabels{ 377 | Labels: map[string]string{ 378 | "parentfield": "parent-value", 379 | "subfield": "sub-value", 380 | }, 381 | } 382 | 383 | v := &WithNested{} 384 | err := Unmarshal(l, v) 385 | assert.NoError(t, err) 386 | assert.Equal(t, "sub-value", v.Nested.SubField) 387 | } 388 | 389 | type WithNestedStructAsPtr struct { 390 | Nested *Nested 391 | } 392 | 393 | func (p *WithNestedStructAsPtr) SetLabels(m map[string]string) { 394 | 395 | } 396 | func TestLabeleeWithNestedStructAsPtr(t *testing.T) { 397 | l := StructWithLabels{ 398 | Labels: map[string]string{ 399 | "parentfield": "parent-value", 400 | "subfield": "sub-value", 401 | }, 402 | } 403 | 404 | v := &WithNestedStructAsPtr{} 405 | err := Unmarshal(l, v) 406 | var p *ParsingError 407 | if errors.As(err, &p) { 408 | t.Log(p.Errors) 409 | } 410 | assert.NoError(t, err) 411 | assert.NotNil(t, v.Nested) 412 | if v.Nested != nil { 413 | assert.Equal(t, "sub-value", v.Nested.SubField) 414 | } 415 | 416 | } 417 | 418 | type NumberBaseStruct struct { 419 | Labels map[string]string `label:"*"` 420 | BinaryInt1 int `label:"binaryint1, base:2"` 421 | BinaryInt2 int `label:"binaryint2, intbase:2"` 422 | BinaryUint1 uint `label:"binaryuint1, base:2"` 423 | BinaryUint2 uint `label:"binaryuint2, uintbase:2"` 424 | } 425 | 426 | func TestBinaryNumbers(t *testing.T) { 427 | 428 | labels := map[string]string{ 429 | "binaryInt1": "111", 430 | "binaryInt2": "11", 431 | "binaryUint1": "111", 432 | "binaryUint2": "11", 433 | } 434 | 435 | input := StructWithLabels{ 436 | Labels: labels, 437 | } 438 | v := &NumberBaseStruct{} 439 | err := Unmarshal(input, v) 440 | assert.NoError(t, err, "Should not have thrown an error") 441 | assert.Equal(t, int(7), v.BinaryInt1, "BinaryInt1 should be set to 7") 442 | assert.Equal(t, int(3), v.BinaryInt2, "BinaryInt2 should be set to 3") 443 | assert.Equal(t, uint(7), v.BinaryUint1, "BinaryUint1 should be set to7") 444 | assert.Equal(t, uint(3), v.BinaryUint2, "BinaryUint2 should be set to 3") 445 | } 446 | 447 | type TestingSliceWithDefaultAndSplit struct { 448 | Slice []int `label:"strings,default:1|2|3,split:|"` 449 | Labels map[string]string `label:"*"` 450 | } 451 | 452 | func TestUnmarshalSliceWithDefault(t *testing.T) { 453 | v := new(TestingSliceWithDefaultAndSplit) 454 | in := make(map[string]string) 455 | 456 | err := Unmarshal(in, v) 457 | assert.NoError(t, err) 458 | assert.Contains(t, v.Slice, 1) 459 | assert.Contains(t, v.Slice, 2) 460 | assert.Contains(t, v.Slice, 3) 461 | 462 | } 463 | 464 | type TestingSlice struct { 465 | Strings []string `label:"strings"` 466 | Ints []int `label:"ints"` 467 | ex string 468 | Labels map[string]string `label:"*"` 469 | } 470 | 471 | func TestUnmarshalSlice(t *testing.T) { 472 | s := []string{"zero", "one", "two", "three", "four"} 473 | n := []string{"0", "1", "2", "3", "4", "2000"} 474 | ni := []int{0, 1, 2, 3, 4, 2000} 475 | 476 | sstr := strings.Join(s, ",") 477 | nstr := strings.Join(n, ",") 478 | m := map[string]string{"strings": sstr, "ints": nstr} 479 | var v TestingSlice 480 | err := Unmarshal(m, &v) 481 | assert.NoError(t, err) 482 | assert.Len(t, v.Strings, 5, "Slice") 483 | assert.Len(t, v.Ints, 6, "SliceInts") 484 | for i, sv := range s { 485 | assert.Equal(t, sv, v.Strings[i]) 486 | } 487 | for i, nv := range ni { 488 | assert.Equal(t, nv, v.Ints[i]) 489 | } 490 | } 491 | 492 | func TestMarshalSlice(t *testing.T) { 493 | s := []string{"zero", "one", "two", "three", "four"} 494 | n := []string{"0", "1", "2", "3", "4", "2000"} 495 | ni := []int{0, 1, 2, 3, 4, 2000} 496 | 497 | sstr := strings.Join(s, ",") 498 | istr := strings.Join(n, ",") 499 | 500 | v := TestingSlice{ 501 | Strings: s, 502 | Ints: ni, 503 | } 504 | 505 | mm, err := Marshal(&v) 506 | assert.NoError(t, err) 507 | assert.Equal(t, mm["strings"], sstr) 508 | assert.Equal(t, mm["ints"], istr) 509 | fmt.Println(mm) 510 | } 511 | 512 | type TestingArray struct { 513 | Strings [5]string `label:"strings"` 514 | Floats [6]float32 `label:"floats"` 515 | Labels map[string]string `label:"*"` 516 | } 517 | 518 | func TestUnmarshalArray(t *testing.T) { 519 | a := [5]string{"zero", "one", "two", "three", "four"} 520 | n := []string{"0.1", "1.2", "2.3", "3.4", "4.5", "2000.0123"} 521 | nf := [6]float32{0.1, 1.2, 2.3, 3.4, 4.5, 2000.0123} 522 | 523 | astr := strings.Join(a[:], ",") 524 | nstr := strings.Join(n, ",") 525 | m := map[string]string{"Strings": astr, "floats": nstr} 526 | var v TestingArray 527 | err := Unmarshal(m, &v) 528 | assert.NoError(t, err) 529 | assert.Len(t, v.Strings, 5, "Strings") 530 | assert.Len(t, v.Floats, 6, "Floats") 531 | for i, sv := range a { 532 | assert.Equal(t, sv, v.Strings[i]) 533 | } 534 | for i, nv := range nf { 535 | assert.Equal(t, nv, v.Floats[i]) 536 | } 537 | 538 | } 539 | 540 | func TestMarshalArray(t *testing.T) { 541 | a := [5]string{"zero", "one", "two", "three", "four"} 542 | n := []string{"0.1", "1.2", "2.3", "3.4", "4.5", "2000.0123"} 543 | nf := [6]float32{0.1, 1.2, 2.3, 3.4, 4.5, 2000.0123} 544 | 545 | astr := strings.Join(a[:], ",") 546 | nstr := strings.Join(n, ",") 547 | v := TestingArray{ 548 | Strings: a, 549 | Floats: nf, 550 | Labels: map[string]string{}, 551 | } 552 | mm, err := Marshal(&v) 553 | 554 | assert.NoError(t, err) 555 | 556 | assert.Equal(t, astr, mm["strings"]) 557 | assert.Equal(t, nstr, mm["floats"]) 558 | } 559 | 560 | func TestOptionValidation(t *testing.T) { 561 | lbl := NewLabeler() 562 | err := lbl.ValidateOptions() 563 | assert.NoError(t, err) 564 | 565 | lbl = NewLabeler(OptTag("")) 566 | err = lbl.ValidateOptions() 567 | assert.Error(t, err, "an error should have occurred due to invalid tag") 568 | 569 | lbl = NewLabeler(OptContainerToken("")) 570 | err = lbl.ValidateOptions() 571 | assert.Error(t, err, "an error should have occurred due to invalid ContainerToken") 572 | 573 | lbl = NewLabeler(OptBaseToken("")) 574 | err = lbl.ValidateOptions() 575 | assert.Error(t, err, "an error should have occurred due to invalid BaseToken") 576 | 577 | lbl = NewLabeler(OptIntBaseToken("")) 578 | err = lbl.ValidateOptions() 579 | assert.Error(t, err, "an error should have occurred due to invalid IntBaseToken") 580 | 581 | lbl = NewLabeler(OptUintBaseToken("")) 582 | err = lbl.ValidateOptions() 583 | assert.Error(t, err, "an error should have occurred due to invalid UintBaseToken") 584 | 585 | lbl = NewLabeler(OptDefaultToken("")) 586 | err = lbl.ValidateOptions() 587 | assert.Error(t, err, "an error should have occurred due to invalid DefaultToken") 588 | 589 | lbl = NewLabeler(OptDiscardToken("")) 590 | err = lbl.ValidateOptions() 591 | assert.Error(t, err, "an error should have occurred due to invalid DiscardToken") 592 | 593 | lbl = NewLabeler(OptFloatFormatToken("")) 594 | err = lbl.ValidateOptions() 595 | assert.Error(t, err, "an error should have occurred due to invalid FloatFormatToken") 596 | 597 | lbl = NewLabeler(OptFormatToken("")) 598 | err = lbl.ValidateOptions() 599 | assert.Error(t, err, "an error should have occurred due to invalid FormatToken") 600 | 601 | lbl = NewLabeler(OptComplexFormatToken("")) 602 | err = lbl.ValidateOptions() 603 | assert.Error(t, err, "an error should have occurred due to invalid ComplexFormatToken") 604 | 605 | lbl = NewLabeler(OptIgnoreCaseToken("")) 606 | err = lbl.ValidateOptions() 607 | assert.Error(t, err, "an error should have occurred due to invalid IgnoreCaseToken") 608 | 609 | lbl = NewLabeler(OptSeparator("")) 610 | err = lbl.ValidateOptions() 611 | assert.Error(t, err, "an error should have occurred due to invalid Separator") 612 | 613 | lbl = NewLabeler(OptAssignmentStr("")) 614 | err = lbl.ValidateOptions() 615 | assert.Error(t, err, "an error should have occurred due to invalid AssignmentStr") 616 | 617 | lbl = NewLabeler(OptTimeFormatToken("")) 618 | err = lbl.ValidateOptions() 619 | assert.Error(t, err, "an error should have occurred due to invalid TimeFormatToken") 620 | 621 | lbl = NewLabeler(OptCaseSensitiveToken("")) 622 | err = lbl.ValidateOptions() 623 | assert.Error(t, err, "an error should have occurred due to invalid CaseSensitiveToken") 624 | 625 | } 626 | 627 | type Private struct { 628 | field string 629 | } 630 | 631 | type StructWithPrivateFields struct { 632 | Labels map[string]string `label:"*"` 633 | private Private 634 | Public string `label:"public"` 635 | } 636 | 637 | func TestIgnoreUnaccessibleFields(t *testing.T) { 638 | l := map[string]string{"public": "value"} 639 | priv := &StructWithPrivateFields{} 640 | err := Unmarshal(l, priv) 641 | assert.NoError(t, err) 642 | assert.Equal(t, l["public"], priv.Public) 643 | } 644 | 645 | // type WithValidation struct { 646 | // Name string `label:"name"` 647 | // Enum MyEnum `label:"enum,required"` 648 | // RequiredField string `label:"required_field,required"` 649 | // Defaulted string `label:"defaulted,default:default value"` 650 | // Labels map[string]string `label:"*"` 651 | // } 652 | 653 | // func TestLabeleeWithValidation(t *testing.T) { 654 | // l := StructWithLabels{ 655 | // Labels: map[string]string{ 656 | // "name": "my name", 657 | // "enum": "X", 658 | // }, 659 | // } 660 | // v := &WithValidation{} 661 | // err := Unmarshal(l, v) 662 | // assert.Error(t, err, "should contain errors") 663 | // var e *ParsingError 664 | // if errors.As(err, &e) { 665 | // assert.Len(t, e.Errors, 2) 666 | // } else { 667 | // assert.Fail(t, "error should be a parsing error") 668 | // } 669 | // assert.Equal(t, "my name", v.Name) 670 | // assert.Equal(t, EnumUnknown, v.Enum) 671 | // } 672 | 673 | // type InvalidDueToMissingLabels struct { 674 | // Name string `label:"name,required"` 675 | // } 676 | 677 | // type InvalidDueMyEnumErr struct { 678 | // Enum MyEnum `label:"enum,required"` 679 | // } 680 | 681 | // func TestInvalidDueToMyEnumReturningError(t *testing.T) { 682 | // l := StructWithLabels{ 683 | // Labels: map[string]string{ 684 | // "enum": "Invalid", 685 | // }, 686 | // } 687 | 688 | // inv := &InvalidDueMyEnumErr{} 689 | // err := Unmarshal(l, inv) 690 | // assert.Error(t, err, "Should have thrown an error") 691 | // assert.Error(t, err) 692 | // if !errors.Is(err, ErrParsing) { 693 | // assert.Fail(t, "Error should be ErrInvalidValue") 694 | // } 695 | // var parsingError *ParsingError 696 | // if errors.As(err, &parsingError) { 697 | // assert.Equal(t, 1, len(parsingError.Errors)) 698 | // fieldErr := parsingError.Errors[0] 699 | // fmt.Println(fieldErr) 700 | // if !errors.Is(fieldErr, ErrExampleInvalidEnum) { 701 | // assert.Fail(t, "Error should be ErrExampleInvalidEnum") 702 | // } else { 703 | // assert.Equal(t, "Enum", fieldErr.Field) 704 | // } 705 | // } else { 706 | // assert.Fail(t, "Error should be a ParsingError") 707 | // } 708 | // t.Log(err) 709 | // } 710 | 711 | // type InvalidDueMultipleRequiredFields struct { 712 | // Enum MyEnum `label:"enum,required"` 713 | // Name string `label:"name,required"` 714 | // } 715 | 716 | // func TestInvalidDueToMultipleRequiredFields(t *testing.T) { 717 | // l := StructWithLabels{ 718 | // Labels: map[string]string{}, 719 | // } 720 | 721 | // inv := &InvalidDueMultipleRequiredFields{} 722 | // err := Unmarshal(l, inv) 723 | // assert.Error(t, err, "Should have thrown an error") 724 | // assert.Error(t, err) 725 | 726 | // var parsingError *ParsingError 727 | // if errors.As(err, &parsingError) { 728 | // assert.Equal(t, 2, len(parsingError.Errors)) 729 | // } else { 730 | // assert.Fail(t, "Error should be a ParsingError") 731 | // } 732 | 733 | // t.Log(err) 734 | // } 735 | -------------------------------------------------------------------------------- /marshalers.go: -------------------------------------------------------------------------------- 1 | package labeler 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "strings" 7 | ) 8 | 9 | type marshalerFunc func(r reflected, o Options) marshalFunc 10 | type marshalerFuncs []marshalerFunc 11 | type marshalFunc func(r reflected, kvs *keyValues, o Options) error 12 | type marshalFieldFunc func(f *field, kvs *keyValues, o Options) error 13 | 14 | func getMarshal(r reflected, o Options) marshalFunc { 15 | switch r.Topic() { 16 | case fieldTopic: 17 | if r.IsContainer(o) { 18 | return containerMarshalers.Marshaler(r, o) 19 | } 20 | return fieldMarshalers.Marshaler(r, o) 21 | case subjectTopic: 22 | return subjectMarshalers.Marshaler(r, o) 23 | case inputTopic: 24 | return inputMarshalers.Marshaler(r, o) 25 | } 26 | return nil 27 | } 28 | 29 | var fieldMarshalers = marshalerFuncs{ 30 | marshalArrayOrSlice, 31 | marshalMarshalerWithOpts, 32 | marshalMarshaler, 33 | marshalFieldPkgString, 34 | marshalFieldStringer, 35 | marshalFieldTextMarshaler, 36 | marshalFieldString, 37 | } 38 | 39 | var collectionMarshalers = marshalerFuncs{ 40 | marshalFieldPkgString, 41 | marshalFieldStringer, 42 | marshalFieldTextMarshaler, 43 | marshalFieldString, 44 | } 45 | 46 | var containerMarshalers = marshalerFuncs{ 47 | marshalMarshalerWithOpts, 48 | marshalMarshaler, 49 | marshalGenericallyLabeled, 50 | marshalLabeled, 51 | marshalMap, 52 | } 53 | 54 | var subjectMarshalers = marshalerFuncs{ 55 | marshalMarshalerWithOpts, 56 | marshalMarshaler, 57 | marshalGenericallyLabeled, 58 | marshalLabeled, 59 | } 60 | 61 | var inputMarshalers = marshalerFuncs{ 62 | marshalGenericallyLabeled, 63 | marshalLabeled, 64 | marshalMap, 65 | } 66 | 67 | func (list marshalerFuncs) Marshaler(r reflected, o Options) marshalFunc { 68 | for _, loader := range list { 69 | marsh := loader(r, o) 70 | if marsh != nil { 71 | return marsh 72 | } 73 | } 74 | return nil 75 | } 76 | 77 | var marshalMarshalerWithOpts = func(r reflected, o Options) marshalFunc { 78 | if !r.CanInterface() || !r.Implements(marshalerWithOptsType) { 79 | return nil 80 | } 81 | return func(r reflected, kvs *keyValues, o Options) error { 82 | u := r.Interface().(MarshalerWithOpts) 83 | m, err := u.MarshalLabels(o) 84 | if err != nil { 85 | return err 86 | } 87 | kvs.Add(m) 88 | return nil 89 | } 90 | } 91 | 92 | var marshalMarshaler = func(r reflected, o Options) marshalFunc { 93 | if !r.CanInterface() || !r.Implements(marshalerType) { 94 | return nil 95 | } 96 | return func(r reflected, kvs *keyValues, o Options) error { 97 | u := r.Interface().(Marshaler) 98 | m, err := u.MarshalLabels() 99 | if err != nil { 100 | return err 101 | } 102 | kvs.Add(m) 103 | return nil 104 | } 105 | } 106 | 107 | var marshalFieldPkgString = func(r reflected, o Options) marshalFunc { 108 | if r.Topic() != fieldTopic { 109 | return nil 110 | } 111 | pkg, ok := fieldStringerPkgs[r.PkgPath()][r.TypeName()] 112 | if !ok { 113 | return nil 114 | } 115 | return pkg.Marshaler(r, o) 116 | } 117 | 118 | var marshalFieldStringer = func(r reflected, o Options) marshalFunc { 119 | if !r.CanInterface() || !r.Implements(stringerType) { 120 | return nil 121 | } 122 | var fstr fieldStringer = func(f *field, o Options) (string, error) { 123 | u := r.Interface().(fmt.Stringer) 124 | return u.String(), nil 125 | } 126 | return fstr.Marshaler(r, o) 127 | } 128 | 129 | var marshalFieldTextMarshaler = func(r reflected, o Options) marshalFunc { 130 | if !r.CanInterface() || !r.Implements(textMarshalerType) { 131 | return nil 132 | } 133 | var fstr fieldStringer = func(f *field, o Options) (string, error) { 134 | u := r.Interface().(TextMarshaler) 135 | t, err := u.MarshalText() 136 | return string(t), err 137 | } 138 | return fstr.Marshaler(r, o) 139 | } 140 | 141 | var marshalFieldString = func(r reflected, o Options) marshalFunc { 142 | strGetter := fieldStringerBasic[r.Kind()] 143 | if strGetter == nil { 144 | return nil 145 | } 146 | return strGetter.Marshaler(r, o) 147 | } 148 | 149 | var marshalArrayOrSlice = func(r reflected, o Options) marshalFunc { 150 | if (!r.IsArray() && !r.IsSlice()) || r.ColType().Implements(stringeeType) || r.IsElem() { 151 | return nil 152 | } 153 | r.SetIsElem(true) 154 | defer r.SetIsElem(false) 155 | 156 | r.PrepCollection() 157 | defer r.ResetCollection() 158 | 159 | fn := collectionMarshalers.Marshaler(r, o) 160 | 161 | if fn == nil { 162 | return nil 163 | } 164 | 165 | return func(r reflected, kvs *keyValues, o Options) error { 166 | r.PrepCollection() 167 | defer r.ResetCollection() 168 | 169 | f := r.(*field) 170 | strs := []string{} 171 | 172 | for i := 0; i < r.Len(); i++ { 173 | r.SetValue(r.ColValue().Index(i)) 174 | if r.deref() { 175 | r.PtrValue().Set(r.Value().Addr()) 176 | } 177 | nkvs := newKeyValues() 178 | 179 | if err := fn(r, &nkvs, o); err != nil { 180 | return err 181 | } 182 | 183 | if v, ok := nkvs.Get(f.key, f.ignoreCase(o)); ok { 184 | strs = append(strs, v.Value) 185 | } 186 | } 187 | 188 | kvs.Set(f.key, strings.Join(strs, f.split(o))) 189 | 190 | return nil 191 | } 192 | } 193 | 194 | var marshalGenericallyLabeled = func(r reflected, o Options) marshalFunc { 195 | if !r.CanInterface() || !r.Implements(genericallyLabeledType) { 196 | return nil 197 | } 198 | return func(r reflected, kvs *keyValues, o Options) error { 199 | u := r.Interface().(GenericallyLabeled) 200 | m := u.GetLabels(o.Tag) 201 | kvs.Add(m) 202 | return nil 203 | } 204 | } 205 | var marshalLabeled = func(r reflected, o Options) marshalFunc { 206 | if !r.CanInterface() || !r.Implements(labeledType) { 207 | return nil 208 | } 209 | return func(r reflected, kvs *keyValues, o Options) error { 210 | u := r.Interface().(Labeled) 211 | m := u.GetLabels() 212 | kvs.Add(m) 213 | return nil 214 | } 215 | } 216 | 217 | var marshalMap = func(r reflected, o Options) marshalFunc { 218 | if !r.Assignable(mapType) { 219 | return nil 220 | } 221 | return func(r reflected, kvs *keyValues, o Options) error { 222 | iter := r.Value().MapRange() 223 | for iter.Next() { 224 | k := iter.Key().String() 225 | v := iter.Value().String() 226 | if o.OmitEmpty && v == "" { 227 | continue 228 | } 229 | kvs.Set(k, v) 230 | } 231 | return nil 232 | } 233 | } 234 | 235 | type fieldStringer func(f *field, o Options) (string, error) 236 | 237 | func (get fieldStringer) Marshaler(r reflected, o Options) marshalFunc { 238 | if r.Topic() != fieldTopic { 239 | return nil 240 | } 241 | return func(r reflected, kvs *keyValues, o Options) error { 242 | f := r.(*field) 243 | s, err := get(f, o) 244 | if err != nil { 245 | return err 246 | } 247 | if s == "" { 248 | s = f.Default(o) 249 | } 250 | if o.OmitEmpty && s == "" { 251 | return nil 252 | } 253 | kvs.Set(f.key, s) 254 | return nil 255 | } 256 | } 257 | 258 | var fieldStringerPkgs = map[string]map[string]fieldStringer{ 259 | "time": { 260 | "Time": func(f *field, o Options) (string, error) { 261 | return f.formatTime(o) 262 | }, 263 | "Duration": func(f *field, o Options) (string, error) { 264 | return f.formatDuration(o) 265 | }, 266 | }, 267 | } 268 | 269 | var fieldStringerBasic = map[reflect.Kind]fieldStringer{ 270 | reflect.Bool: func(f *field, o Options) (string, error) { 271 | return f.formatBool(o) 272 | }, 273 | reflect.Float64: func(f *field, o Options) (string, error) { 274 | return f.formatFloat(o) 275 | }, 276 | reflect.Float32: func(f *field, o Options) (string, error) { 277 | return f.formatFloat(o) 278 | }, 279 | reflect.Int: func(f *field, o Options) (string, error) { 280 | return f.formatInt(o) 281 | }, 282 | reflect.Int8: func(f *field, o Options) (string, error) { 283 | return f.formatInt(o) 284 | }, 285 | reflect.Int16: func(f *field, o Options) (string, error) { 286 | return f.formatInt(o) 287 | }, 288 | reflect.Int32: func(f *field, o Options) (string, error) { 289 | return f.formatInt(o) 290 | }, 291 | reflect.Int64: func(f *field, o Options) (string, error) { 292 | return f.formatInt(o) 293 | }, 294 | reflect.String: func(f *field, o Options) (string, error) { 295 | return f.formatString(o) 296 | }, 297 | reflect.Uint: func(f *field, o Options) (string, error) { 298 | return f.formatUint(o) 299 | }, 300 | reflect.Uint8: func(f *field, o Options) (string, error) { 301 | return f.formatUint(o) 302 | }, 303 | reflect.Uint16: func(f *field, o Options) (string, error) { 304 | return f.formatUint(o) 305 | }, 306 | reflect.Uint32: func(f *field, o Options) (string, error) { 307 | return f.formatUint(o) 308 | }, 309 | reflect.Uint64: func(f *field, o Options) (string, error) { 310 | return f.formatUint(o) 311 | }, 312 | reflect.Complex64: func(f *field, o Options) (string, error) { 313 | return f.formatComplex(o) 314 | }, 315 | reflect.Complex128: func(f *field, o Options) (string, error) { 316 | return f.formatComplex(o) 317 | }, 318 | } 319 | -------------------------------------------------------------------------------- /meta.go: -------------------------------------------------------------------------------- 1 | package labeler 2 | 3 | import ( 4 | "reflect" 5 | ) 6 | 7 | type reflected interface { 8 | Meta() *meta 9 | Save() 10 | Topic() topic 11 | Path() string 12 | Type() reflect.Type 13 | IsStruct() bool 14 | NumField() int 15 | Value() reflect.Value 16 | Implements(u reflect.Type) bool 17 | Interface() interface{} 18 | Assignable(u reflect.Type) bool 19 | CanInterface() bool 20 | CanAddr() bool 21 | CanSet() bool 22 | StructField(i int) (reflect.StructField, bool) 23 | ValueField(i int) (reflect.Value, bool) 24 | StoreValue(reflect.Value) 25 | TypeName() string 26 | PkgPath() string 27 | IsPtr() bool 28 | PtrValue() reflect.Value 29 | Kind() reflect.Kind 30 | Unmarshal(kvs *keyValues, o Options) error 31 | Marshal(kvs *keyValues, o Options) error 32 | IsContainer(o Options) bool 33 | ColType() reflect.Type 34 | SetValue(reflect.Value) 35 | IsArray() bool 36 | IsSlice() bool 37 | Len() int 38 | ColValue() reflect.Value 39 | IsElem() bool 40 | SetIsElem(bool) 41 | deref() bool 42 | PrepCollection() 43 | ResetCollection() 44 | ColElemKind() reflect.Kind 45 | ColElemType() reflect.Type 46 | } 47 | 48 | type topic int 49 | 50 | const ( 51 | invalidTopic = iota 52 | fieldTopic 53 | subjectTopic 54 | inputTopic 55 | ) 56 | 57 | //TODO: rename the values below and create interface accessors where applicable 58 | type meta struct { 59 | typ reflect.Type 60 | kind reflect.Kind 61 | value reflect.Value 62 | field reflect.StructField 63 | addr reflect.Value 64 | ptrType reflect.Type 65 | colValue reflect.Value 66 | colType reflect.Type 67 | colKind reflect.Kind 68 | colElemType reflect.Type 69 | colElemKind reflect.Kind 70 | addrType reflect.Type 71 | ptrValue reflect.Value 72 | typeName string 73 | pkgPath string 74 | numField int 75 | isPtr bool 76 | isArray bool 77 | isSlice bool 78 | canAddr bool 79 | canSet bool 80 | canInterface bool 81 | len int 82 | isElem bool 83 | marshal marshalFunc 84 | unmarshal unmarshalFunc 85 | // unmarshaler unmarshaler 86 | } 87 | 88 | func newMeta(rv reflect.Value) meta { 89 | m := meta{ 90 | value: rv, 91 | kind: rv.Kind(), 92 | typ: rv.Type(), 93 | } 94 | m.canSet = m.value.CanSet() 95 | m.canAddr = m.value.CanAddr() 96 | m.canInterface = m.value.CanInterface() 97 | m.checkArraySlice() 98 | m.isPtr = m.deref() 99 | 100 | m.typeName = m.typ.Name() 101 | m.pkgPath = m.typ.PkgPath() 102 | if m.canAddr { 103 | m.addr = m.value.Addr() 104 | m.addrType = m.addr.Type() 105 | } 106 | if m.kind == reflect.Struct { 107 | m.numField = m.typ.NumField() 108 | } 109 | 110 | return m 111 | } 112 | func (m *meta) ColElemKind() reflect.Kind { 113 | return m.colElemKind 114 | } 115 | 116 | func (m *meta) ColElemType() reflect.Type { 117 | return m.colElemType 118 | } 119 | 120 | func (m *meta) Interface() interface{} { 121 | if m.isPtr { 122 | return m.ptrValue.Interface() 123 | } 124 | if m.value.CanAddr() && !m.addr.IsZero() { 125 | return m.addr.Interface() 126 | } 127 | return m.value.Interface() 128 | } 129 | 130 | func (m *meta) Type() reflect.Type { 131 | return m.typ 132 | } 133 | 134 | func (m *meta) ColType() reflect.Type { 135 | return m.colType 136 | } 137 | 138 | func (m *meta) IsElem() bool { 139 | return m.isElem 140 | } 141 | 142 | func (m *meta) SetIsElem(v bool) { 143 | m.isElem = v 144 | } 145 | 146 | func (m *meta) StoreValue(v reflect.Value) { 147 | m.value = v 148 | } 149 | 150 | func (m *meta) PkgPath() string { 151 | return m.pkgPath 152 | } 153 | 154 | func (m *meta) TypeName() string { 155 | return m.typeName 156 | } 157 | 158 | func (m *meta) ColValue() reflect.Value { 159 | return m.colValue 160 | } 161 | func (m *meta) IsSlice() bool { 162 | return m.isSlice 163 | } 164 | 165 | func (m *meta) IsArray() bool { 166 | return m.isArray 167 | } 168 | 169 | func (m *meta) Len() int { 170 | return m.len 171 | } 172 | func (m *meta) Kind() reflect.Kind { 173 | return m.kind 174 | } 175 | 176 | func (m *meta) checkArraySlice() bool { 177 | 178 | if m.kind != reflect.Slice && m.kind != reflect.Array { 179 | return false 180 | } 181 | 182 | m.len = m.value.Len() 183 | m.isSlice = m.kind == reflect.Slice 184 | m.isArray = m.kind == reflect.Array 185 | 186 | if m.isSlice && m.value.IsNil() { 187 | m.value.Set(reflect.New(m.typ).Elem()) 188 | } 189 | m.colType = m.typ 190 | m.colValue = m.value 191 | m.colKind = m.kind 192 | 193 | return true 194 | } 195 | 196 | func (m *meta) PrepCollection() { 197 | m.value = reflect.New(m.typ).Elem() 198 | m.typ = m.typ.Elem() 199 | m.kind = m.typ.Kind() 200 | m.colElemKind = m.kind 201 | m.colElemType = m.typ 202 | m.isElem = true 203 | } 204 | 205 | func (m *meta) ResetCollection() { 206 | if !m.isArray && !m.isSlice { 207 | return 208 | } 209 | m.typ = m.colType 210 | m.kind = m.colKind 211 | m.value = m.colValue 212 | m.isElem = false 213 | } 214 | 215 | func (m *meta) deref() bool { 216 | if m.kind != reflect.Ptr { 217 | return false 218 | } 219 | var ptr reflect.Value 220 | if m.value.IsNil() { 221 | elem := m.typ.Elem() 222 | ptr = reflect.New(elem).Elem() 223 | } else { 224 | ptr = m.value.Elem() 225 | } 226 | m.ptrValue = m.value 227 | m.ptrType = m.typ 228 | m.value = ptr 229 | m.typ = ptr.Type() 230 | m.kind = ptr.Kind() 231 | 232 | return true 233 | 234 | } 235 | 236 | func (m *meta) SetValue(rv reflect.Value) { 237 | m.value = rv 238 | } 239 | 240 | func (m *meta) IsStruct() bool { 241 | return m.kind == reflect.Struct 242 | } 243 | 244 | func (m *meta) save() { 245 | 246 | if m.isPtr && m.CanSet() { 247 | m.ptrValue.Set(m.value.Addr()) 248 | } 249 | } 250 | 251 | func (m *meta) IsPtr() bool { 252 | return m.isPtr 253 | } 254 | 255 | func (m *meta) PtrValue() reflect.Value { 256 | return m.ptrValue 257 | } 258 | 259 | func (m *meta) Meta() *meta { 260 | return m 261 | } 262 | 263 | func (m meta) NumField() int { 264 | return m.numField 265 | } 266 | 267 | func (m meta) Implements(u reflect.Type) bool { 268 | name := m.typeName 269 | _ = name 270 | uname := u.Name() 271 | _ = uname 272 | if m.isPtr && m.ptrType.Implements(u) { 273 | return true 274 | } 275 | if m.typ.Implements(u) { 276 | return true 277 | } 278 | 279 | if !m.canAddr { 280 | return false 281 | } 282 | return m.addrType.Implements(u) 283 | } 284 | 285 | func (m meta) Assignable(u reflect.Type) bool { 286 | return u.AssignableTo(m.typ) 287 | } 288 | 289 | func (m meta) CanSet() bool { 290 | return m.canSet 291 | } 292 | 293 | func (m meta) CanInterface() bool { 294 | return m.canInterface 295 | } 296 | func (m meta) CanAddr() bool { 297 | return m.canAddr 298 | } 299 | 300 | func (m meta) Value() reflect.Value { 301 | return m.value 302 | } 303 | 304 | func (m meta) ValueField(i int) (reflect.Value, bool) { 305 | if m.kind != reflect.Struct && m.value.Kind() != reflect.Struct { 306 | return reflect.Value{}, false 307 | } 308 | if i >= m.numField { 309 | return reflect.Value{}, false 310 | } 311 | return m.value.Field(i), true 312 | } 313 | func (m meta) StructField(i int) (reflect.StructField, bool) { 314 | if m.kind != reflect.Struct { 315 | return reflect.StructField{}, false 316 | } 317 | if i >= m.numField { 318 | return reflect.StructField{}, false 319 | } 320 | return m.typ.Field(i), true 321 | } 322 | -------------------------------------------------------------------------------- /options.go: -------------------------------------------------------------------------------- 1 | package labeler 2 | 3 | import ( 4 | "reflect" 5 | "strings" 6 | ) 7 | 8 | func getDefaultOptions() Options { 9 | o := Options{ 10 | Tag: "label", 11 | ContainerToken: "*", 12 | FloatFormat: 'f', 13 | ComplexFormat: 'f', 14 | IntBase: 10, 15 | UintBase: 10, 16 | OmitEmpty: true, 17 | IgnoreCase: true, 18 | KeepLabels: true, 19 | DefaultToken: "default", 20 | FormatToken: "format", 21 | FloatFormatToken: "floatformat", 22 | ComplexFormatToken: "complexformat", 23 | TimeFormatToken: "timeformat", 24 | CaseSensitiveToken: "casesensitive", 25 | OmitEmptyToken: "omitempty", 26 | IncludeEmptyToken: "includeempty", 27 | IgnoreCaseToken: "ignorecase", 28 | KeepToken: "keep", 29 | DiscardToken: "discard", 30 | BaseToken: "base", 31 | UintBaseToken: "uintbase", 32 | IntBaseToken: "intbase", 33 | SplitToken: "split", 34 | Separator: ",", 35 | AssignmentStr: ":", 36 | TimeFormat: "", 37 | ContainerField: "", 38 | Split: ",", 39 | // CaseSensitiveTokens: true, 40 | // RequireAllFields: false, 41 | // RequiredToken: "required", 42 | // NotRequiredToken: "notrequired", 43 | 44 | } 45 | 46 | return o 47 | } 48 | 49 | var defaultTokenParsers = getTokenParsers(getDefaultOptions()) 50 | 51 | // Options are the configurable options allowed when Unmarshaling/Marshaling. 52 | // Tokens are not case sensitive unless the option CaseSensitiveToken is true. 53 | type Options struct { 54 | 55 | // default: "label" 56 | // Tag is the tag to lookup. 57 | Tag string `option:"token"` 58 | // default: "," 59 | // This is the divider / separator between tag options, configurable in the 60 | // event keys or default values happen to contain "," 61 | Separator string `option:"token"` 62 | 63 | // default: "," 64 | // What arrays and slices are split on 65 | Split string 66 | // default: true 67 | // Determines whether or not to case sensitivity should apply to labels. 68 | // this is overridden if `label:"*,ignorecase"` or `label:"*,casesensitive" 69 | IgnoreCase bool 70 | // default: "" 71 | // If blank, the field is assumed accessible through GetLabels / SetLabels, 72 | // Unmarshal/Marshal or tag `label:"*"`. If none of these are applicable an 73 | // error will be returned from Unmarshal / Marshal. 74 | ContainerField string 75 | 76 | // default: true 77 | // KeepLabels Determines whether or not to keep labels that were unmarshaled into 78 | // fields. Individual fields can override this setting at the field level by 79 | // appending the KeepToken (default: "keep") or the DiscardToken (default: "discard"). 80 | // Example: MyField string `label:"myField,keep"` 81 | // Example: MyField string `label:"myField, discard"` 82 | 83 | // This can be set at the container level 84 | // Example: `label:"*, keep"` or `label:"*, discard"` 85 | KeepLabels bool 86 | 87 | // default: true 88 | // OmitEmpty Determines whether or not to assign labels that were empty / zero value 89 | // Individual fields can override this setting at the field level by appending 90 | // OmitEmptyToken (default: "omitempty") or IncludeEmptyToken (default: "incldueempty") 91 | // This can also be set with by attaching 92 | // This can be set at the container level 93 | // Example: `label:"*, omitempty"` or `label:"*, includeempty"` 94 | OmitEmpty bool 95 | 96 | // // default: false 97 | // // RequireAllFields Determines whether or not all fields are required 98 | // // Individual fields can override this setting at the field level by appending 99 | // // "required", "notrequired", or a custom configured RequiredToken or NotRequiredToken. 100 | // // 101 | // // Example: 102 | // // MyField string `label:"myField,required"` // required 103 | // // MyField string `label:"myField,notrequired"` // not required 104 | // RequireAllFields bool 105 | 106 | // default: "" 107 | // Default sets a global default value for all fields not available in the labels. 108 | Default string 109 | 110 | //default: true 111 | 112 | // default: "" 113 | // TimeFormat sets the default format to parse times with. Can be overridden at the tag level 114 | // or set with the * field. 115 | TimeFormat string 116 | 117 | // default: 10 118 | // IntBase is the default base while parsing int, int64, int32, int16, int8 119 | IntBase int 120 | 121 | // default: 10 122 | // UintBase is the default base while parsing uint, uint64, uint32, uint16, uint8 123 | UintBase int 124 | 125 | // default: "*" 126 | // 127 | // ContainerToken identifies the field as the container for the labels associated to 128 | // Options.Tag (default: "label"). 129 | // Using a field level container tag is not mandatory. Implementing an appropriate interface 130 | // is also possible. 131 | // 132 | // Example: 133 | // type MyStruct struct { 134 | // Labels map[string]string `label:"*"` 135 | // } 136 | // 137 | // Example: 138 | // type Attributes map[string]string 139 | // 140 | // type MyStruct struct { 141 | // Attrs Attributes `attr:"_all"` 142 | // } 143 | // labeler.Marshal(v, OptTag("attr"), OptContainerToken("_all")) 144 | ContainerToken string `option:"token"` 145 | 146 | // default: "default" 147 | // DefaultToken is the token used at the tag level to determine the default value for the 148 | // given field if it is not present in the labels map. 149 | DefaultToken string `option:"token"` 150 | 151 | // // default: "required" 152 | // // RequiredToken is the token used at the tag level to set the field as being required 153 | // RequiredToken string `option:"token"` 154 | 155 | // // default: "notrequired" 156 | // // NotRequiredToken is the token used at the tag level to set the field as being not required 157 | // NotRequiredToken string `option:"token"` 158 | 159 | // default: "keep" 160 | // KeepToken is the token used at the tag level to indicate that the field should be carried over 161 | // to the labels container (through SetLabels or direct assignment) regardless of global settings 162 | KeepToken string `option:"token"` 163 | 164 | // default: "discard" 165 | // DiscardToken is the token used at the tag level to indicate that the field should be discarded 166 | // and not assigned to labels (through SetLabels or direct assignment) regardless of global settings 167 | DiscardToken string `option:"token"` 168 | 169 | // default: "casesensitive" 170 | // CaseSensitive is the token used at the tag level to indicate that the key for the labels lookup 171 | // is case sensitive regardless of global settings 172 | CaseSensitiveToken string `option:"token"` 173 | 174 | // default: "ignorecase" 175 | // IgnoreCaseToken is the token used at the tag level to indicate that the key for the labels lookup 176 | // is case insensitive regardless of global settings 177 | IgnoreCaseToken string `option:"token"` 178 | 179 | // default: `omitempty` 180 | // OmitEmptyToken is the token used at the tag level to determine whether or not to include empty, zero 181 | // values in the labels and whether to assign empty values. 182 | OmitEmptyToken string `option:"token"` 183 | 184 | // default: `includeempty` 185 | // IncludeEmptyToken is the token used at the tag level to determine whether or not to include empty, zero 186 | // values in the labels and whether to assign empty values. 187 | IncludeEmptyToken string `option:"token"` 188 | 189 | // default: ":" 190 | // AssignmentStr is used to assign values, such as default (default value) or format 191 | AssignmentStr string `option:"token"` 192 | 193 | // default: "Format" 194 | // LayoutToken is used to assign the format of a field. This is only used for time.Time at the moment. 195 | FormatToken string `option:"token"` 196 | 197 | // // default: true 198 | // // CaseSensitiveTokens determines whether or not tokens, such as floatformat or uintbase, 199 | // // can be of any case, such as floatFormat, or UintBase respectively. 200 | // CaseSensitiveTokens bool 201 | 202 | // default: 'f' 203 | //FloatFormat is used to determine the format for formatting float fields 204 | FloatFormat byte `option:"floatformat"` 205 | 206 | // FloatFormatToken is used to differentiate the target format, primarily to be used on container tags, 207 | // however it can be used on a float field if preferred. 208 | // default: floatformat 209 | FloatFormatToken string `option:"token"` 210 | 211 | // ComplexFormatToken is used to differentiate the target format, primarily to be used on container tags, 212 | // however it can be used on a complex field if preferred. 213 | // default: complexformat 214 | ComplexFormatToken string `option:"token"` 215 | 216 | // default: 'f' 217 | //ComplexFormat is used to determine the format for formatting complex fields 218 | ComplexFormat byte `option:"floatformat"` 219 | // TimeFormatToken is used to differentiate the target format, primarily to be used on container tags, 220 | // however it can be used on a time field if preferred. 221 | // default: timeformat 222 | TimeFormatToken string `option:"token"` 223 | 224 | // BaseToken sets the token for parsing base for int, int64, int32, int16, int8, uint, uint64, uint32, uint16, uint8 225 | BaseToken string `option:"token"` 226 | // UintBaseToken sets the token for parsing base for uint, uint64, uint32, uint16, uint8 227 | UintBaseToken string `option:"token"` 228 | // IntBaseToken sets the token for parsing base for int, int64, int32, int16, int8 229 | IntBaseToken string `option:"token"` 230 | SplitToken string `option:"token"` 231 | 232 | tokenParsers tagTokenParsers 233 | 234 | unmarshaling bool 235 | } 236 | 237 | // FromTag sets options from t if t is on a container field (either marked as a container with a tag set 238 | // to Options.ContainerFlag) or Options.ContainerField 239 | // Options that can be updated from the tag are: 240 | // FloatFormat, TimeFormat (via TimeFormatToken), KeepLabels (via Options.KeepToken / Options.DiscardToken), 241 | // IgnoreCase (via Options.IgnoreCaseToken) 242 | // returns: true if successful, false otherwise 243 | func (o Options) FromTag(t *Tag) Options { 244 | if t == nil { 245 | return o 246 | } 247 | if t.ComplexFormat != 0 { 248 | o.ComplexFormat = t.ComplexFormat 249 | } 250 | if t.FloatFormat != 0 { 251 | o.FloatFormat = t.FloatFormat 252 | } 253 | if t.UintBaseIsSet { 254 | o.UintBase = t.UintBase 255 | } 256 | if t.IntBaseIsSet { 257 | o.IntBase = t.IntBase 258 | } 259 | if t.TimeFormat != "" { 260 | o.TimeFormat = t.TimeFormat 261 | } 262 | if t.KeepIsSet { 263 | o.KeepLabels = t.Keep 264 | } 265 | if t.IgnoreCaseIsSet { 266 | o.IgnoreCase = t.IgnoreCase 267 | } 268 | if t.OmitEmptyIsSet { 269 | o.OmitEmpty = true 270 | } 271 | if t.IncludeEmptyIsSet { 272 | o.OmitEmpty = false 273 | } 274 | // if t.RequiredIsSet { 275 | // o.RequireAllFields = t.Required 276 | // } 277 | return o 278 | } 279 | 280 | // Option is a function which accepts *Options, allowing for configuration 281 | type Option func(o *Options) 282 | 283 | // OptCaseSensitive Sets IgnoreCase to false 284 | func OptCaseSensitive() Option { 285 | return func(o *Options) { 286 | o.IgnoreCase = false 287 | } 288 | } 289 | 290 | // OptContainerField sets the ContainerField. This is overridden if a field contains the tag `label:"*"` 291 | func OptContainerField(field string) Option { 292 | return func(o *Options) { 293 | o.ContainerField = field 294 | } 295 | } 296 | 297 | // OptKeepLabels sets Options.KeepLabels to true, keeping all labels that were unmarshaled, 298 | // including those that were unmarshaled into fields. 299 | func OptKeepLabels() Option { 300 | return func(o *Options) { 301 | o.KeepLabels = true 302 | } 303 | } 304 | 305 | // OptDiscardLabels sets Options.KeepLabels to false, discarding all labels that were 306 | // unmarshaled into other fields. 307 | func OptDiscardLabels() Option { 308 | return func(o *Options) { 309 | o.KeepLabels = false 310 | } 311 | } 312 | 313 | // // OptRequireAllFields sets Options.Required to true, thus causing all fields with a tag to be required. 314 | // func OptRequireAllFields() Option { 315 | // return func(o *Options) { 316 | // o.RequireAllFields = true 317 | // } 318 | // } 319 | 320 | // OptSeparator sets the Separator option to s. This allows for tags to have a different separator string other than "," 321 | // such as MyField string `label:"mykey|default:has,commas"` 322 | func OptSeparator(s string) Option { 323 | return func(o *Options) { 324 | o.Separator = s 325 | } 326 | } 327 | 328 | // OptSplit sets the Split option to s which is used to split and join arrays. 329 | func OptSplit(s string) Option { 330 | return func(o *Options) { 331 | o.Split = s 332 | } 333 | } 334 | 335 | // OptTag sets the Tag option to v. This allows for tags to have a different handle other than "label" 336 | // such as MyField string `lbl:"mykey|default:whatev"` 337 | func OptTag(v string) Option { 338 | return func(o *Options) { 339 | o.Tag = v 340 | } 341 | } 342 | 343 | // OptContainerToken sets the ContainerToken option to v. 344 | // ContainerToken sets the string to match for a field marking the label container. 345 | // Using a field level container tag is not mandatory. Implementing an appropriate interface 346 | // or using a setting is safer as tag settings take precedent over options while some options can not 347 | // be set at the container tag level (TimeFormat, ContainerFlag, Tag, Separator) 348 | func OptContainerToken(v string) Option { 349 | return func(o *Options) { 350 | o.ContainerToken = v 351 | } 352 | } 353 | 354 | // OptSplitToken sets the ContainerToken option to v. 355 | // SplitToken sets the string to split / join arrays and slices with. 356 | func OptSplitToken(v string) Option { 357 | return func(o *Options) { 358 | o.Split = v 359 | } 360 | } 361 | 362 | // OptDefaultToken sets the DefaultToken option to v. 363 | // DefaultToken is the token used at the tag level to determine the default value for the 364 | // given field if it is not present in the labels map. Default is "default." Change if 365 | // "default:" could occur in your label keys 366 | func OptDefaultToken(v string) Option { 367 | return func(o *Options) { 368 | o.DefaultToken = v 369 | } 370 | } 371 | 372 | // OptAssignmentStr sets the AssignmentStr option to v. 373 | // AssignmentStr is used to assign values, such as default (default value) or format (time) 374 | func OptAssignmentStr(v string) Option { 375 | return func(o *Options) { 376 | o.AssignmentStr = v 377 | } 378 | } 379 | 380 | // OptTimeFormat sets the TimeFormat option to v. 381 | func OptTimeFormat(v string) Option { 382 | return func(o *Options) { 383 | o.TimeFormat = v 384 | } 385 | } 386 | 387 | // OptFloatFormat Sets the global FloatFormat to use in FormatFloat. Optiosn are 'b', 'e', 'E', 'f', 'g', 'G', 'x', 'X' 388 | func OptFloatFormat(fmt byte) Option { 389 | return func(o *Options) { 390 | o.FloatFormat = fmt 391 | 392 | } 393 | } 394 | 395 | // OptComplexFormat Sets the global ComplexFormat to use in ComplexFloat. Optiosn are 'b', 'e', 'E', 'f', 'g', 'G', 'x', 'X' 396 | func OptComplexFormat(fmt byte) Option { 397 | return func(o *Options) { 398 | o.ComplexFormat = fmt 399 | 400 | } 401 | } 402 | 403 | // OptFormatToken sets the token used for indicating format at the field level. 404 | func OptFormatToken(v string) Option { 405 | return func(o *Options) { 406 | o.FormatToken = v 407 | } 408 | } 409 | 410 | // // OptRequiredToken sets RequiredToken to v 411 | // func OptRequiredToken(v string) Option { 412 | // return func(o *Options) { 413 | // o.RequiredToken = v 414 | // } 415 | // } 416 | 417 | // // OptNotRequiredToken sets NotRequiredToken to v 418 | // func OptNotRequiredToken(v string) Option { 419 | // return func(o *Options) { 420 | // o.NotRequiredToken = v 421 | // } 422 | // } 423 | 424 | // OptIgnoreCaseToken sets the IgnoreCaseToken to v 425 | func OptIgnoreCaseToken(v string) Option { 426 | return func(o *Options) { 427 | o.IgnoreCaseToken = v 428 | } 429 | } 430 | 431 | // OptCaseSensitiveToken sets CaseSensitiveToken to v 432 | func OptCaseSensitiveToken(v string) Option { 433 | return func(o *Options) { 434 | o.CaseSensitiveToken = v 435 | } 436 | } 437 | 438 | // OptKeepToken sets KeepToken to v 439 | func OptKeepToken(v string) Option { 440 | return func(o *Options) { 441 | o.KeepToken = v 442 | } 443 | } 444 | 445 | // OptDiscardToken sets DiscardToken to v 446 | func OptDiscardToken(v string) Option { 447 | return func(o *Options) { 448 | o.DiscardToken = v 449 | } 450 | } 451 | 452 | // OptFloatFormatToken sets FloatFormatToken to v 453 | func OptFloatFormatToken(v string) Option { 454 | return func(o *Options) { 455 | o.FloatFormatToken = v 456 | } 457 | } 458 | 459 | // OptComplexFormatToken sets ComplexFormatToken to v 460 | func OptComplexFormatToken(v string) Option { 461 | return func(o *Options) { 462 | o.ComplexFormatToken = v 463 | } 464 | } 465 | 466 | // OptTimeFormatToken sets TimeFormatToken to v 467 | func OptTimeFormatToken(v string) Option { 468 | return func(o *Options) { 469 | o.TimeFormatToken = v 470 | } 471 | } 472 | 473 | // OptIntBaseToken sets the IntBaseToken 474 | func OptIntBaseToken(v string) Option { 475 | return func(o *Options) { 476 | o.IntBaseToken = v 477 | } 478 | } 479 | 480 | // OptUintBaseToken sets the IntBaseToken 481 | func OptUintBaseToken(v string) Option { 482 | return func(o *Options) { 483 | o.UintBaseToken = v 484 | } 485 | } 486 | 487 | // OptBaseToken sets the IntBaseToken 488 | func OptBaseToken(v string) Option { 489 | return func(o *Options) { 490 | o.BaseToken = v 491 | } 492 | } 493 | 494 | // OptUintBase sets UintBase 495 | func OptUintBase(v int) Option { 496 | return func(o *Options) { 497 | o.UintBase = v 498 | } 499 | } 500 | 501 | // OptIntBase sets IntBase 502 | func OptIntBase(v int) Option { 503 | return func(o *Options) { 504 | o.IntBase = v 505 | } 506 | } 507 | 508 | func newOptions(opts []Option) Options { 509 | o := getDefaultOptions() 510 | if len(opts) > 0 { 511 | for _, execOpt := range opts { 512 | execOpt(&o) 513 | } 514 | // o.tokenSensitivity() 515 | o.tokenParsers = getTokenParsers(o) 516 | } else { 517 | o.tokenParsers = defaultTokenParsers 518 | } 519 | return o 520 | } 521 | 522 | // func (o *Options) tokenSensitivity() { 523 | // if !o.CaseSensitiveTokens { 524 | // rv := reflect.ValueOf(o) 525 | // rt := reflect.TypeOf(o) 526 | // numField := rt.NumField() 527 | // for i := 0; i < numField; i++ { 528 | // fv := rv.Field(i) 529 | // sf := rt.Field(i) 530 | // if t, tagged := sf.Tag.Lookup("option"); tagged { 531 | // if t == "token" { 532 | // v := fv.String() 533 | // v = strings.ToLower(v) 534 | // fv.SetString(v) 535 | // } 536 | // } 537 | // } 538 | // } 539 | // } 540 | 541 | // Validate checks to see if Options are valid, returning an OptionError if not. 542 | func (o Options) Validate() error { 543 | rv := reflect.ValueOf(o) 544 | rt := reflect.TypeOf(o) 545 | for i := 0; i < rt.NumField(); i++ { 546 | fv := rv.Field(i) 547 | sf := rt.Field(i) 548 | if t, tagged := sf.Tag.Lookup("option"); tagged { 549 | v := strings.TrimSpace(fv.String()) 550 | if t == "token" { 551 | if v == "" { 552 | return NewOptionError(sf.Name, " is required") 553 | } 554 | } 555 | } 556 | } 557 | return nil 558 | } 559 | 560 | var floatFormatOptions [8]byte = [8]byte{'b', 'e', 'E', 'f', 'g', 'G', 'x', 'X'} 561 | 562 | func isValidFloatFormat(f byte) bool { 563 | for _, b := range floatFormatOptions { 564 | if f == b { 565 | return true 566 | } 567 | } 568 | return false 569 | } 570 | -------------------------------------------------------------------------------- /subject.go: -------------------------------------------------------------------------------- 1 | package labeler 2 | 3 | import ( 4 | "errors" 5 | "reflect" 6 | "sync" 7 | ) 8 | 9 | type subject struct { 10 | meta 11 | fieldset 12 | } 13 | 14 | func newSubject(v interface{}, o Options) (subject, error) { 15 | rv := reflect.ValueOf(v) 16 | 17 | if rv.Kind() != reflect.Ptr { 18 | return subject{}, ErrInvalidValue 19 | } 20 | sub := subject{ 21 | meta: newMeta(rv), 22 | fieldset: newFieldset(), 23 | } 24 | 25 | sub.marshal = getMarshal(&sub, o) 26 | sub.unmarshal = getUnmarshal(&sub, o) 27 | err := sub.init(o) 28 | return sub, err 29 | } 30 | 31 | func (sub *subject) init(o Options) error { 32 | ch := newChannels(sub, o) 33 | go ch.processFields() 34 | errs := []*FieldError{} 35 | fieldCh := ch.fieldCh 36 | errCh := ch.errCh 37 | for fieldCh != nil || errCh != nil { 38 | select { 39 | case f, ok := <-fieldCh: 40 | if !ok { 41 | fieldCh = nil 42 | break 43 | } 44 | err := sub.processField(f, o) 45 | if err != nil { 46 | return err 47 | } 48 | case err, ok := <-errCh: 49 | if !ok { 50 | errCh = nil 51 | break 52 | } 53 | var fieldErr *FieldError 54 | if errors.As(err, &fieldErr) { 55 | errs = append(errs, fieldErr) 56 | } else { 57 | return err 58 | } 59 | } 60 | } 61 | if len(errs) > 0 { 62 | return NewParsingError(errs) 63 | } 64 | o = o.FromTag(sub.containerTag()) 65 | return nil 66 | } 67 | 68 | func (sub *subject) Save() { 69 | sub.save() 70 | } 71 | 72 | func (sub *subject) Unmarshal(kvs *keyValues, o Options) error { 73 | if sub.unmarshal == nil && (sub.container == nil || sub.container.unmarshal == nil) { 74 | return ErrMissingContainer 75 | } 76 | fieldErrs := []*FieldError{} 77 | for _, f := range sub.tagged { 78 | err := f.Unmarshal(kvs, o) 79 | if err != nil { 80 | fieldErrs = append(fieldErrs, f.err(err)) 81 | } 82 | if f.ShouldDiscard(o) { 83 | kvs.Delete(f.key) 84 | } 85 | if f.wasSet { 86 | f.Save() 87 | } 88 | } 89 | if len(fieldErrs) > 0 { 90 | return NewParsingError(fieldErrs) 91 | } 92 | if sub.unmarshal != nil { 93 | return sub.unmarshal(sub, kvs, o) 94 | } 95 | return sub.container.Unmarshal(kvs, o) 96 | } 97 | 98 | func (sub *subject) Marshal(kvs *keyValues, o Options) error { 99 | if sub.marshal == nil && (sub.container == nil || sub.container.marshal == nil) { 100 | return ErrMissingContainer 101 | } 102 | fieldErrs := []*FieldError{} 103 | for _, f := range sub.tagged { 104 | err := f.Marshal(kvs, o) 105 | if err != nil { 106 | fieldErrs = append(fieldErrs, f.err(err)) 107 | } 108 | } 109 | if len(fieldErrs) > 0 { 110 | return NewParsingError(fieldErrs) 111 | } 112 | if sub.marshal != nil { 113 | return sub.marshal(sub, kvs, o) 114 | } 115 | return sub.container.Marshal(kvs, o) 116 | } 117 | 118 | func (sub *subject) Path() string { 119 | return "" 120 | } 121 | 122 | func (sub *subject) Topic() topic { 123 | return subjectTopic 124 | } 125 | 126 | func (sub *subject) IsContainer(o Options) bool { 127 | return false 128 | } 129 | 130 | // should rename this 131 | type channels struct { 132 | fieldCh chan *field 133 | errCh chan error 134 | reflected reflected 135 | waitGroup *sync.WaitGroup 136 | options Options 137 | } 138 | 139 | func newChannels(r reflected, o Options) channels { 140 | numField := r.NumField() 141 | wg := &sync.WaitGroup{} 142 | wg.Add(numField) 143 | ch := channels{ 144 | reflected: r, 145 | options: o, 146 | fieldCh: make(chan *field, numField), 147 | errCh: make(chan error, numField), 148 | waitGroup: wg, 149 | } 150 | return ch 151 | } 152 | 153 | func (ch channels) pipe(w channels) { 154 | errCh := ch.errCh 155 | fieldCh := ch.fieldCh 156 | for errCh != nil || fieldCh != nil { 157 | select { 158 | case err, ok := <-errCh: 159 | if !ok { 160 | errCh = nil 161 | break 162 | } 163 | w.errCh <- err 164 | case f, ok := <-fieldCh: 165 | if !ok { 166 | fieldCh = nil 167 | break 168 | } 169 | w.fieldCh <- f 170 | } 171 | } 172 | } 173 | 174 | func (ch channels) finished() { 175 | ch.waitGroup.Wait() 176 | close(ch.errCh) 177 | close(ch.fieldCh) 178 | } 179 | 180 | func (ch channels) processFields() { 181 | defer ch.finished() 182 | ch.waitGroup.Add(1) 183 | numField := ch.reflected.NumField() 184 | for i := 0; i < numField; i++ { 185 | go ch.processField(i) 186 | } 187 | ch.waitGroup.Done() 188 | } 189 | 190 | func (ch channels) processField(i int) { 191 | defer ch.waitGroup.Done() 192 | f, err := newField(ch.reflected, i, ch.options) 193 | if err != nil { 194 | ch.errCh <- err 195 | return 196 | } 197 | switch { 198 | case f.isTagged: 199 | ch.fieldCh <- f 200 | case f.IsContainer(ch.options): 201 | ch.fieldCh <- f 202 | case f.IsStruct() && f.canInterface: 203 | wch := newChannels(f, ch.options) 204 | go wch.processFields() 205 | wch.pipe(ch) 206 | // wch.waitGroup.Wait() 207 | default: 208 | ch.fieldCh <- f 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /tag.go: -------------------------------------------------------------------------------- 1 | package labeler 2 | 3 | import ( 4 | "strconv" 5 | "strings" 6 | ) 7 | 8 | // Tag is parsed Struct Tag 9 | type Tag struct { 10 | Raw string 11 | Key string 12 | Default string 13 | IsContainer bool 14 | IgnoreCase bool 15 | Required bool 16 | Keep bool 17 | Format string 18 | TimeFormat string 19 | FloatFormat byte 20 | ComplexFormat byte 21 | Base int 22 | IntBase int 23 | UintBase int 24 | BaseIsSet bool 25 | KeepIsSet bool 26 | UintBaseIsSet bool 27 | IntBaseIsSet bool 28 | IgnoreCaseIsSet bool 29 | RequiredIsSet bool 30 | DefaultIsSet bool 31 | OmitEmptyIsSet bool 32 | IncludeEmptyIsSet bool 33 | Split string 34 | } 35 | 36 | // NewTag creates a new Tag from a string and Options. 37 | func newTag(tagStr string, o Options) (*Tag, error) { 38 | t := &Tag{ 39 | Raw: tagStr, 40 | } 41 | tagStr = strings.TrimSpace(tagStr) 42 | 43 | if tagStr == "" || tagStr == o.Separator { 44 | return t, ErrMalformedTag 45 | } 46 | 47 | tokens := strings.Split(tagStr, o.Separator) 48 | t.Key = strings.TrimSpace(tokens[0]) 49 | 50 | if t.Key == o.ContainerToken { 51 | t.IsContainer = true 52 | } 53 | if len(tokens) == 1 { 54 | return t, nil 55 | } 56 | for _, s := range tokens[1:] { 57 | err := parseToken(t, s, o) 58 | if err != nil { 59 | return t, err 60 | } 61 | } 62 | return t, nil 63 | } 64 | 65 | func (t *Tag) processToken(key string, token string, o Options) error { 66 | 67 | return ErrMalformedTag 68 | 69 | } 70 | 71 | // GetIntBase returns the tag's int base from either Options.IntBaseToken or Options.BaseToken 72 | func (t Tag) GetIntBase() (int, bool) { 73 | if t.IntBaseIsSet { 74 | return t.IntBase, true 75 | } 76 | if t.BaseIsSet { 77 | return t.Base, true 78 | } 79 | return 0, false 80 | } 81 | 82 | // GetUintBase returns the tag's int base from either Options.UintBaseToken or Options.BaseToken 83 | func (t Tag) GetUintBase() (int, bool) { 84 | if t.UintBaseIsSet { 85 | return t.UintBase, true 86 | } 87 | if t.BaseIsSet { 88 | return t.Base, true 89 | } 90 | return 0, false 91 | } 92 | 93 | // GetComplexFormat returns the tag's complex format from either Options.ComplexFormatToken or Options.FormatToken 94 | func (t *Tag) GetComplexFormat() (byte, bool) { 95 | if t.ComplexFormat != 0 { 96 | return t.ComplexFormat, true 97 | } 98 | if t.Format != "" { 99 | return t.Format[0], true 100 | } 101 | return 0, false 102 | } 103 | 104 | // GetFloatFormat returns the tag's float format from either Options.FloatFormatToken or Options.FormatToken 105 | func (t *Tag) GetFloatFormat() (byte, bool) { 106 | if t.FloatFormat != 0 { 107 | return t.FloatFormat, true 108 | } 109 | if t.Format != "" { 110 | return t.Format[0], true 111 | } 112 | return 0, false 113 | } 114 | 115 | // GetTimeFormat returns the tag's time format from either Options.TimeFormatToken or Options.FormatToken 116 | func (t Tag) GetTimeFormat() (string, bool) { 117 | if t.TimeFormat != "" { 118 | return t.TimeFormat, true 119 | } 120 | if t.Format != "" { 121 | return t.Format, true 122 | } 123 | return "", false 124 | } 125 | 126 | // GetSplit returns the tag's split string 127 | func (t Tag) GetSplit() (string, bool) { 128 | if t.Split != "" { 129 | return t.Split, true 130 | } 131 | return "", false 132 | } 133 | 134 | // SetIgnoreCase set' the field's or container's IgnoreCase for label keys 135 | func (t *Tag) setIgnoreCase(v bool) error { 136 | if t.IgnoreCaseIsSet { 137 | return ErrMalformedTag 138 | } 139 | t.IgnoreCase = v 140 | t.IgnoreCaseIsSet = true 141 | return nil 142 | } 143 | 144 | // // SetRequired sets the field's or container's Require / RequireAlLFields (respectively) for labels 145 | // func (t *Tag) setRequired(v bool) error { 146 | // if t.RequiredIsSet { 147 | // return ErrMalformedTag 148 | // } 149 | // t.Required = v 150 | // t.RequiredIsSet = true 151 | // return nil 152 | // } 153 | 154 | // SetKeep sets the field's or container's Keep / Discard of labels 155 | func (t *Tag) setKeep(v bool) error { 156 | if t.KeepIsSet { 157 | return ErrMalformedTag 158 | } 159 | t.Keep = v 160 | t.KeepIsSet = true 161 | return nil 162 | } 163 | 164 | func (t *Tag) setOmitEmpty() error { 165 | if t.OmitEmptyIsSet { 166 | return ErrMalformedTag 167 | } 168 | t.OmitEmptyIsSet = true 169 | return nil 170 | } 171 | 172 | func (t *Tag) setIncludeEmpty() error { 173 | if t.OmitEmptyIsSet { 174 | return ErrMalformedTag 175 | } 176 | t.IncludeEmptyIsSet = true 177 | return nil 178 | } 179 | 180 | // SetDefault set's the field's or container's default value 181 | func (t *Tag) setDefault(s string) error { 182 | if t.DefaultIsSet { 183 | return ErrMalformedTag 184 | } 185 | t.DefaultIsSet = true 186 | t.Default = s 187 | return nil 188 | } 189 | 190 | // SetFormat sets the field's format value 191 | func (t *Tag) setFormat(s string) error { 192 | t.Format = s 193 | return nil 194 | } 195 | 196 | // SetTimeFormat sets the field's or container's time format 197 | func (t *Tag) setTimeFormat(s string) error { 198 | t.TimeFormat = s 199 | 200 | return nil 201 | } 202 | 203 | // SetFloatFormat sets the field's or container's float format 204 | func (t *Tag) setFloatFormat(s string) error { 205 | v := s[0] 206 | if !isValidFloatFormat(v) { 207 | return ErrInvalidFloatFormat 208 | } 209 | t.FloatFormat = v 210 | return nil 211 | } 212 | 213 | // SetComplexFormat sets the field's or container's complex format 214 | func (t *Tag) setComplexFormat(s string) error { 215 | v := s[0] 216 | if !isValidFloatFormat(v) { 217 | return ErrInvalidFloatFormat 218 | } 219 | t.ComplexFormat = v 220 | 221 | return nil 222 | 223 | } 224 | 225 | // SetBase sets the field's int/uint base 226 | func (t *Tag) setBase(s string) error { 227 | v, err := strconv.Atoi(s) 228 | if err != nil { 229 | return err 230 | } 231 | t.Base = v 232 | t.BaseIsSet = true 233 | return nil 234 | } 235 | 236 | // SetIntBase sets the field's or container's int base 237 | func (t *Tag) setIntBase(s string) error { 238 | v, err := strconv.Atoi(s) 239 | if err != nil { 240 | return err 241 | } 242 | t.IntBaseIsSet = true 243 | t.IntBase = v 244 | return nil 245 | } 246 | 247 | // SetUintBase sets the field's or container's uint base 248 | func (t *Tag) setUintBase(s string) error { 249 | v, err := strconv.Atoi(s) 250 | if err != nil { 251 | return err 252 | } 253 | t.UintBaseIsSet = true 254 | t.UintBase = v 255 | return nil 256 | //int 257 | } 258 | 259 | func (t *Tag) setSplit(s string) error { 260 | if len(s) == 0 { 261 | return ErrSplitEmpty 262 | } 263 | t.Split = s 264 | return nil 265 | } 266 | 267 | type tagTokenParser func(t *Tag, tt tagToken, o Options) error 268 | type tagTokenParsers map[string]tagTokenParser 269 | 270 | type tagToken struct { 271 | key string // 272 | text string // raw text, whiiiiiiiich, now that I think about it, I need to resolve below. 273 | value string // for assignments 274 | 275 | } 276 | 277 | func parseToken(t *Tag, s string, o Options) error { 278 | s = strings.TrimSpace(s) 279 | key := s 280 | 281 | // if !o.CaseSensitiveTokens { 282 | // key = strings.ToLower(key) 283 | // } 284 | 285 | tt := tagToken{ 286 | key: key, 287 | text: s, 288 | } 289 | i := strings.Index(key, o.AssignmentStr) 290 | if i > -1 { 291 | tt.key = strings.TrimSpace(key[:i]) 292 | if i == len(s) { 293 | return ErrMalformedTag 294 | } 295 | tt.value = strings.TrimSpace(s[i+1:]) 296 | } 297 | 298 | parser := o.tokenParsers[tt.key] 299 | if parser == nil { 300 | return ErrMalformedTag 301 | } 302 | return parser(t, tt, o) 303 | } 304 | 305 | func getTokenParsers(o Options) tagTokenParsers { 306 | return tagTokenParsers{ 307 | o.IgnoreCaseToken: parseIgnoreCase, 308 | o.CaseSensitiveToken: parseCaseSensitive, 309 | o.DiscardToken: parseDiscard, 310 | o.KeepToken: parseKeep, 311 | o.TimeFormatToken: parseTimeFormat, 312 | o.ComplexFormatToken: parseComplexFormat, 313 | o.DefaultToken: parseDefault, 314 | o.FloatFormatToken: parseFloatFormat, 315 | o.IntBaseToken: parseIntBase, 316 | o.UintBaseToken: parseUintBase, 317 | o.BaseToken: parseBase, 318 | o.FormatToken: parseFormat, 319 | o.IncludeEmptyToken: parseIncludeEmpty, 320 | o.OmitEmptyToken: parseOmitEmpty, 321 | o.SplitToken: parseSplit, 322 | // o.RequiredToken: parseRequired, 323 | // o.NotRequiredToken: parseNotRquired, 324 | } 325 | 326 | } 327 | 328 | var parseSplit = func(t *Tag, tt tagToken, o Options) error { 329 | return t.setSplit(tt.value) 330 | } 331 | 332 | var parseIncludeEmpty = func(t *Tag, tt tagToken, o Options) error { 333 | return t.setIncludeEmpty() 334 | } 335 | var parseOmitEmpty = func(t *Tag, tt tagToken, o Options) error { 336 | return t.setOmitEmpty() 337 | } 338 | var parseBase = func(t *Tag, tt tagToken, o Options) error { 339 | return t.setBase(tt.value) 340 | } 341 | var parseUintBase = func(t *Tag, tt tagToken, o Options) error { 342 | return t.setUintBase(tt.value) 343 | } 344 | var parseIntBase = func(t *Tag, tt tagToken, o Options) error { 345 | return t.setIntBase(tt.value) 346 | } 347 | var parseFloatFormat = func(t *Tag, tt tagToken, o Options) error { 348 | return t.setFloatFormat(tt.value) 349 | } 350 | var parseDefault = func(t *Tag, tt tagToken, o Options) error { 351 | return t.setDefault(tt.value) 352 | } 353 | var parseComplexFormat = func(t *Tag, tt tagToken, o Options) error { 354 | return t.setComplexFormat(tt.value) 355 | } 356 | var parseTimeFormat = func(t *Tag, tt tagToken, o Options) error { 357 | return t.setTimeFormat(tt.value) 358 | } 359 | var parseIgnoreCase = func(t *Tag, tt tagToken, o Options) error { 360 | return t.setIgnoreCase(false) 361 | } 362 | var parseCaseSensitive = func(t *Tag, tt tagToken, o Options) error { 363 | return t.setIgnoreCase(false) 364 | } 365 | var parseDiscard = func(t *Tag, tt tagToken, o Options) error { 366 | return t.setKeep(false) 367 | } 368 | var parseKeep = func(t *Tag, tt tagToken, o Options) error { 369 | return t.setKeep(true) 370 | } 371 | var parseFormat = func(t *Tag, tt tagToken, o Options) error { 372 | return t.setFormat(tt.value) 373 | } 374 | 375 | // var parseRequired = func(t *Tag, tt tagToken, o Options) error { 376 | // return t.setRequired(true) 377 | // } 378 | 379 | // var parseNotRquired = func(t *Tag, tt tagToken, o Options) error { 380 | // return t.setRequired(false) 381 | // } 382 | -------------------------------------------------------------------------------- /unmarshalers.go: -------------------------------------------------------------------------------- 1 | package labeler 2 | 3 | import ( 4 | "reflect" 5 | "strings" 6 | ) 7 | 8 | type unmarshalerFunc func(r reflected, o Options) unmarshalFunc 9 | type unmarshalFunc func(r reflected, kvs *keyValues, o Options) error 10 | type unmarshalFieldFunc func(f *field, kvs *keyValues, o Options) error 11 | type unmarshalerFuncs []unmarshalerFunc 12 | 13 | func getUnmarshal(r reflected, o Options) unmarshalFunc { 14 | switch r.Topic() { 15 | case fieldTopic: 16 | if r.IsContainer(o) { 17 | return containerUnmarshalers.Unmarshaler(r, o) 18 | } 19 | return fieldUnmarshalers.Unmarshaler(r, o) 20 | case subjectTopic: 21 | return subjectUnmarshalers.Unmarshaler(r, o) 22 | } 23 | return nil 24 | } 25 | 26 | var fieldUnmarshalers = unmarshalerFuncs{ 27 | unmarshalSlice, 28 | unmarshalArray, 29 | unmarshalUnmarshalerWithOpts, 30 | unmarshalUnmarshaler, 31 | unmarshalFieldPkgString, 32 | unmarshalFieldStringee, 33 | unmarshalFieldTextUnmarshaler, 34 | unmarshalFieldString, 35 | } 36 | 37 | var collectionUnmarshalers = unmarshalerFuncs{ 38 | unmarshalFieldPkgString, 39 | unmarshalFieldStringee, 40 | unmarshalFieldTextUnmarshaler, 41 | unmarshalFieldString, 42 | } 43 | 44 | var containerUnmarshalers = unmarshalerFuncs{ 45 | unmarshalUnmarshalerWithOpts, 46 | unmarshalUnmarshaler, 47 | unmarshalGenericLabelee, 48 | unmarshalStrictLabelee, 49 | unmarshalLabelee, 50 | unmarshalMap, 51 | } 52 | 53 | var subjectUnmarshalers = unmarshalerFuncs{ 54 | unmarshalUnmarshalerWithOpts, 55 | unmarshalUnmarshaler, 56 | unmarshalGenericLabelee, 57 | unmarshalStrictLabelee, 58 | unmarshalLabelee, 59 | } 60 | 61 | func (list unmarshalerFuncs) Unmarshaler(r reflected, o Options) unmarshalFunc { 62 | for _, loader := range list { 63 | unmarsh := loader(r, o) 64 | if unmarsh != nil { 65 | return unmarsh 66 | } 67 | } 68 | return nil 69 | } 70 | 71 | func (set unmarshalFieldFunc) Unmarshaler(r reflected, o Options) unmarshalFunc { 72 | if r.Topic() != fieldTopic { 73 | return nil 74 | } 75 | return func(r reflected, kvs *keyValues, o Options) error { 76 | return set(r.(*field), kvs, o) 77 | } 78 | } 79 | 80 | var unmarshalMap = func(r reflected, o Options) unmarshalFunc { 81 | if r.Topic() != fieldTopic || !r.CanSet() || !r.Assignable(mapType) { 82 | return nil 83 | } 84 | var set unmarshalFieldFunc = func(f *field, kvs *keyValues, o Options) error { 85 | return f.setMap(kvs.Map(), o) 86 | } 87 | return set.Unmarshaler(r, o) 88 | } 89 | 90 | var unmarshalUnmarshaler = func(r reflected, o Options) unmarshalFunc { 91 | if !r.CanInterface() || !r.Implements(unmarshalerType) { 92 | return nil 93 | } 94 | return func(r reflected, kvs *keyValues, o Options) error { 95 | u := r.Interface().(Unmarshaler) 96 | return u.UnmarshalLabels(kvs.Map()) 97 | } 98 | } 99 | 100 | var unmarshalUnmarshalerWithOpts = func(r reflected, o Options) unmarshalFunc { 101 | if !r.CanInterface() || !r.Implements(unmarshalerWithOptsType) { 102 | return nil 103 | } 104 | return func(r reflected, kvs *keyValues, o Options) error { 105 | u := r.Interface().(UnmarshalerWithOpts) 106 | return u.UnmarshalLabels(kvs.Map(), o) 107 | } 108 | } 109 | 110 | var unmarshalLabelee = func(r reflected, o Options) unmarshalFunc { 111 | if !r.CanInterface() || !r.Implements(labeleeType) { 112 | return nil 113 | } 114 | return func(r reflected, kvs *keyValues, o Options) error { 115 | u := r.Interface().(Labelee) 116 | u.SetLabels(kvs.Map()) 117 | return nil 118 | } 119 | } 120 | 121 | var unmarshalStrictLabelee = func(r reflected, o Options) unmarshalFunc { 122 | if !r.CanInterface() || !r.Implements(strictLabeleeType) { 123 | return nil 124 | } 125 | 126 | return func(r reflected, kvs *keyValues, o Options) error { 127 | u := r.Interface().(StrictLabelee) 128 | u.SetLabels(kvs.Map()) 129 | return nil 130 | } 131 | } 132 | 133 | var unmarshalGenericLabelee = func(r reflected, o Options) unmarshalFunc { 134 | if !r.CanInterface() || !r.Implements(genericLabeleeType) { 135 | return nil 136 | } 137 | return func(r reflected, kvs *keyValues, o Options) error { 138 | u := r.Interface().(Labelee) 139 | u.SetLabels(kvs.Map()) 140 | return nil 141 | } 142 | } 143 | 144 | var unmarshalFieldStringee = func(r reflected, o Options) unmarshalFunc { 145 | if !r.CanInterface() || !r.Implements(stringeeType) { 146 | return nil 147 | } 148 | var fstr fieldStrUnmarshalFunc = func(f *field, s string, o Options) error { 149 | u := f.Interface().(Stringee) 150 | u.FromString(s) 151 | return nil 152 | } 153 | return fstr.Unmarshaler(r, o) 154 | } 155 | 156 | var unmarshalFieldTextUnmarshaler = func(r reflected, o Options) unmarshalFunc { 157 | if !r.CanInterface() || !r.Implements(textUnmarshalerType) { 158 | return nil 159 | } 160 | u := r.Interface().(TextUnmarshaler) 161 | var set fieldStrUnmarshalFunc = func(f *field, s string, o Options) error { 162 | return u.UnmarshalText([]byte(s)) 163 | } 164 | return set.Unmarshaler(r, o) 165 | } 166 | 167 | var unmarshalFieldPkgString = func(r reflected, o Options) unmarshalFunc { 168 | if r.Topic() != fieldTopic { 169 | return nil 170 | } 171 | set, ok := fieldStringeePkgs[r.PkgPath()][r.TypeName()] 172 | if !ok { 173 | return nil 174 | } 175 | return set.Unmarshaler(r, o) 176 | } 177 | 178 | var unmarshalFieldString = func(r reflected, o Options) unmarshalFunc { 179 | if !r.CanSet() { 180 | return nil 181 | } 182 | if fss, ok := fieldStringeeBasic[r.Kind()]; ok { 183 | return fss.Unmarshaler(r, o) 184 | } 185 | return nil 186 | } 187 | 188 | func splitFieldValue(f *field, kvs *keyValues, o Options) ([]string, bool) { 189 | kv, ok := kvs.Get(f.key, f.ignoreCase(o)) 190 | var s string 191 | switch { 192 | case ok: 193 | s = kv.Value 194 | case f.HasDefault(o): 195 | s = f.Default(o) 196 | case f.OmitEmpty(o): 197 | return nil, false 198 | } 199 | strs := strings.Split(s, f.split(o)) 200 | return strs, true 201 | } 202 | 203 | func unmarshalElem(f *field, rv reflect.Value, s string, fn unmarshalFunc, o Options) error { 204 | f.SetValue(rv) 205 | isPtr := f.deref() 206 | nkvs := newKeyValues() 207 | 208 | nkvs.Set(f.key, s) 209 | 210 | if err := fn(f, &nkvs, o); err != nil { 211 | return err 212 | } 213 | 214 | if isPtr { 215 | f.PtrValue().Set(f.Value().Addr()) 216 | } 217 | 218 | return nil 219 | } 220 | 221 | var unmarshalArray = func(r reflected, o Options) unmarshalFunc { 222 | if !r.IsArray() || r.ColType().Implements(stringeeType) || r.IsElem() { 223 | return nil 224 | } 225 | 226 | r.SetIsElem(true) 227 | defer r.SetIsElem(false) 228 | 229 | r.PrepCollection() 230 | defer r.ResetCollection() 231 | 232 | fn := collectionUnmarshalers.Unmarshaler(r, o) 233 | if fn == nil { 234 | return nil 235 | } 236 | 237 | return func(r reflected, kvs *keyValues, o Options) error { 238 | r.PrepCollection() 239 | defer r.ResetCollection() 240 | 241 | f := r.(*field) 242 | strs, hasVal := splitFieldValue(f, kvs, o) 243 | if !hasVal { 244 | return nil 245 | } 246 | for i, s := range strs { 247 | if i >= f.len { 248 | break 249 | } 250 | rv := f.ColValue().Index(i) 251 | if err := unmarshalElem(f, rv, s, fn, o); err != nil { 252 | return err 253 | } 254 | } 255 | 256 | return nil 257 | } 258 | } 259 | 260 | var unmarshalSlice = func(r reflected, o Options) unmarshalFunc { 261 | if !r.IsSlice() || r.ColType().Implements(stringeeType) || r.IsElem() { 262 | return nil 263 | } 264 | 265 | r.PrepCollection() 266 | defer r.ResetCollection() 267 | 268 | fn := collectionUnmarshalers.Unmarshaler(r, o) 269 | if fn == nil { 270 | return nil 271 | } 272 | 273 | return func(r reflected, kvs *keyValues, o Options) error { 274 | r.PrepCollection() 275 | defer r.ResetCollection() 276 | f := r.(*field) 277 | strs, hasVal := splitFieldValue(f, kvs, o) 278 | if !hasVal { 279 | return nil 280 | } 281 | for _, s := range strs { 282 | rv := reflect.New(r.Type()).Elem() 283 | if err := unmarshalElem(f, rv, s, fn, o); err != nil { 284 | return err 285 | } 286 | r.ColValue().Set(reflect.Append(r.ColValue(), r.Value())) 287 | } 288 | 289 | return nil 290 | } 291 | } 292 | 293 | type fieldStrUnmarshalFunc func(f *field, s string, o Options) error 294 | 295 | func (setStr fieldStrUnmarshalFunc) Unmarshaler(r reflected, o Options) unmarshalFunc { 296 | if r.Topic() != fieldTopic { 297 | return nil 298 | } 299 | return func(r reflected, kvs *keyValues, o Options) error { 300 | f := r.(*field) 301 | kv, ok := kvs.Get(f.key, f.ignoreCase(o)) 302 | var s string 303 | switch { 304 | case ok: 305 | s = kv.Value 306 | case f.HasDefault(o): 307 | s = f.Default(o) 308 | case f.OmitEmpty(o): 309 | return nil 310 | } 311 | f.wasSet = true 312 | return setStr(f, s, o) 313 | } 314 | } 315 | 316 | var fieldStringeePkgs = map[string]map[string]fieldStrUnmarshalFunc{ 317 | "time": { 318 | "Time": func(f *field, s string, o Options) error { 319 | return f.setTime(s, o) 320 | }, 321 | "Duration": func(f *field, s string, o Options) error { 322 | return f.setDuration(s, o) 323 | }, 324 | }, 325 | } 326 | 327 | var fieldStringeeBasic = map[reflect.Kind]fieldStrUnmarshalFunc{ 328 | reflect.Bool: func(f *field, s string, o Options) error { 329 | return f.setBool(s, o) 330 | }, 331 | reflect.Float64: func(f *field, s string, o Options) error { 332 | return f.setFloat(s, 64, o) 333 | }, 334 | reflect.Float32: func(f *field, s string, o Options) error { 335 | return f.setFloat(s, 32, o) 336 | }, 337 | reflect.Int: func(f *field, s string, o Options) error { 338 | return f.setInt(s, 0, o) 339 | }, 340 | reflect.Int8: func(f *field, s string, o Options) error { 341 | return f.setInt(s, 8, o) 342 | }, 343 | reflect.Int16: func(f *field, s string, o Options) error { 344 | return f.setInt(s, 16, o) 345 | }, 346 | reflect.Int32: func(f *field, s string, o Options) error { 347 | return f.setInt(s, 32, o) 348 | }, 349 | reflect.Int64: func(f *field, s string, o Options) error { 350 | return f.setInt(s, 64, o) 351 | }, 352 | reflect.String: func(f *field, s string, o Options) error { 353 | return f.setString(s, o) 354 | }, 355 | reflect.Uint: func(f *field, s string, o Options) error { 356 | return f.setUint(s, 0, o) 357 | }, 358 | reflect.Uint8: func(f *field, s string, o Options) error { 359 | return f.setUint(s, 8, o) 360 | }, 361 | reflect.Uint16: func(f *field, s string, o Options) error { 362 | return f.setUint(s, 16, o) 363 | }, 364 | reflect.Uint32: func(f *field, s string, o Options) error { 365 | return f.setUint(s, 32, o) 366 | }, 367 | reflect.Uint64: func(f *field, s string, o Options) error { 368 | return f.setUint(s, 64, o) 369 | }, 370 | reflect.Complex64: func(f *field, s string, o Options) error { 371 | return f.setComplex(s, 64, o) 372 | }, 373 | reflect.Complex128: func(f *field, s string, o Options) error { 374 | return f.setComplex(s, 128, o) 375 | }, 376 | } 377 | --------------------------------------------------------------------------------