-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add a new mangler that allows us to have aliases for dials tags to facilitate migrating from one name to another seamlessly. If both the old name and the new name are set, an error will be returned because the expectation is that you're using one or the other exclusively. Also, fix a bug in `ez` where an error thrown by a Source may cause the process to hang. And, lastly, specifically exclude TextUnmarshaller implementing types from mangling.
- Loading branch information
1 parent
402b482
commit 5d5cfaa
Showing
9 changed files
with
384 additions
and
35 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,22 @@ | ||
// Package common provides constants that are used among different dials sources | ||
package common | ||
|
||
// DialsTagName is the name of the dials tag. | ||
const DialsTagName = "dials" | ||
const ( | ||
// DialsTagName is the name of the dials tag. | ||
DialsTagName = "dials" | ||
|
||
// DialsEnvTagName is the name of the dialsenv tag. | ||
DialsEnvTagName = "dialsenv" | ||
|
||
// DialsFlagTagName is the name of the dialsflag tag. | ||
DialsFlagTagName = "dialsflag" | ||
|
||
// DialsPFlagTagName is the name of the dialspflag tag. | ||
DialsPFlagTag = "dialspflag" | ||
|
||
// DialsFlagAliasTag is the name of the dialsflagalias tag. | ||
DialsPFlagShortTag = "dialspflagshort" | ||
|
||
// HelpTextTag is the name of the struct tag for flag descriptions | ||
DialsHelpTextTag = "dialsdesc" | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
package transform | ||
|
||
import ( | ||
"fmt" | ||
"reflect" | ||
|
||
"github.com/fatih/structtag" | ||
"github.com/vimeo/dials/common" | ||
) | ||
|
||
const ( | ||
dialsAliasTagSuffix = "alias" | ||
aliasFieldSuffix = "_alias9wr876rw3" // a random string to append to the alias field to avoid collisions | ||
) | ||
|
||
// the list of tags that we should search for aliases | ||
var aliasSourceTags = []string{ | ||
common.DialsTagName, | ||
common.DialsFlagTagName, | ||
common.DialsEnvTagName, | ||
common.DialsFlagTagName, | ||
common.DialsPFlagTag, | ||
common.DialsPFlagShortTag, | ||
} | ||
|
||
// AliasMangler manages aliases for dials, dialsenv, dialsflag, and dialspflag | ||
// struct tags to make it possible to migrate from one name to another | ||
// conveniently. | ||
type AliasMangler struct{} | ||
|
||
// Mangle implements the Mangler interface. If an alias tag is defined, the | ||
// struct field will be copied with the non-aliased tag set to the alias's | ||
// value. | ||
func (a AliasMangler) Mangle(sf reflect.StructField) ([]reflect.StructField, error) { | ||
originalVals := map[string]string{} | ||
aliasVals := map[string]string{} | ||
|
||
sfTags, parseErr := structtag.Parse(string(sf.Tag)) | ||
if parseErr != nil { | ||
return nil, fmt.Errorf("error parsing source tags %w", parseErr) | ||
} | ||
|
||
anyAliasFound := false | ||
for _, tag := range aliasSourceTags { | ||
if originalVal, getErr := sfTags.Get(tag); getErr == nil { | ||
originalVals[tag] = originalVal.Name | ||
} | ||
|
||
if aliasVal, getErr := sfTags.Get(tag + dialsAliasTagSuffix); getErr == nil { | ||
aliasVals[tag] = aliasVal.Name | ||
anyAliasFound = true | ||
|
||
// remove the alias tag from the definition | ||
sfTags.Delete(tag + dialsAliasTagSuffix) | ||
} | ||
} | ||
|
||
if !anyAliasFound { | ||
// we didn't find any aliases so just get out early | ||
return []reflect.StructField{sf}, nil | ||
} | ||
|
||
aliasField := sf | ||
aliasField.Name += aliasFieldSuffix | ||
|
||
// now that we've copied it, reset the struct tags on the source field to | ||
// not include the alias tags | ||
sf.Tag = reflect.StructTag(sfTags.String()) | ||
|
||
tags, parseErr := structtag.Parse(string(aliasField.Tag)) | ||
if parseErr != nil { | ||
return nil, fmt.Errorf("error parsing struct tags: %w", parseErr) | ||
} | ||
|
||
for _, tag := range aliasSourceTags { | ||
// remove the alias tag so it's not left on the copied StructField | ||
tags.Delete(tag + dialsAliasTagSuffix) | ||
|
||
if aliasVals[tag] == "" { | ||
// if the particular flag isn't set at all just move on... | ||
continue | ||
} | ||
|
||
newDialsTag := &structtag.Tag{ | ||
Key: tag, | ||
Name: aliasVals[tag], | ||
} | ||
|
||
if setErr := tags.Set(newDialsTag); setErr != nil { | ||
return nil, fmt.Errorf("error setting new value for dials tag: %w", setErr) | ||
} | ||
|
||
// update dialsdesc if there is one | ||
if desc, getErr := tags.Get("dialsdesc"); getErr == nil { | ||
newDesc := &structtag.Tag{ | ||
Key: "dialsdesc", | ||
Name: desc.Name + " (alias of `" + originalVals[tag] + "`)", | ||
} | ||
if setErr := tags.Set(newDesc); setErr != nil { | ||
return nil, fmt.Errorf("error setting amended dialsdesc for tag %q: %w", tag, setErr) | ||
} | ||
} | ||
} | ||
|
||
// set the new flags on the alias field | ||
aliasField.Tag = reflect.StructTag(tags.String()) | ||
|
||
return []reflect.StructField{sf, aliasField}, nil | ||
} | ||
|
||
// Unmangle implements the Mangler interface and unwinds the alias copying | ||
// operation. Note that if both the source and alias are both set in the | ||
// configuration, an error will be returned. | ||
func (a AliasMangler) Unmangle(sf reflect.StructField, fvs []FieldValueTuple) (reflect.Value, error) { | ||
switch len(fvs) { | ||
case 1: | ||
// if there's only one tuple that means there was no alias, so just | ||
// return... | ||
return fvs[0].Value, nil | ||
case 2: | ||
// two means there's an alias so we should continue on... | ||
default: | ||
return reflect.Value{}, fmt.Errorf("expected 1 or 2 tuples, got %d", len(fvs)) | ||
} | ||
|
||
if !fvs[0].Value.IsNil() && !fvs[1].Value.IsNil() { | ||
return reflect.Value{}, fmt.Errorf("both alias and original set for field %q", sf.Name) | ||
} | ||
|
||
// return the first one that isn't nil | ||
for _, fv := range fvs { | ||
if !fv.Value.IsNil() { | ||
return fv.Value, nil | ||
} | ||
} | ||
|
||
// if we made it this far, they were both nil, which is fine -- just return | ||
// one of them. | ||
return fvs[0].Value, nil | ||
} | ||
|
||
// ShouldRecurse is called after Mangle for each field so nested struct | ||
// fields get iterated over after any transformation done by Mangle(). | ||
func (a AliasMangler) ShouldRecurse(_ reflect.StructField) bool { | ||
return true | ||
} |
Oops, something went wrong.