|
| 1 | +package transform |
| 2 | + |
| 3 | +import ( |
| 4 | + "fmt" |
| 5 | + "reflect" |
| 6 | + |
| 7 | + "github.com/fatih/structtag" |
| 8 | + "github.com/vimeo/dials/common" |
| 9 | +) |
| 10 | + |
| 11 | +const ( |
| 12 | + dialsAliasTagSuffix = "alias" |
| 13 | + aliasFieldSuffix = "_alias9wr876rw3" // a random string to append to the alias field to avoid collisions |
| 14 | +) |
| 15 | + |
| 16 | +// the list of tags that we should search for aliases |
| 17 | +var aliasSourceTags = []string{ |
| 18 | + common.DialsTagName, |
| 19 | + common.DialsFlagTagName, |
| 20 | + common.DialsEnvTagName, |
| 21 | + common.DialsFlagTagName, |
| 22 | + common.DialsPFlagTag, |
| 23 | + common.DialsPFlagShortTag, |
| 24 | +} |
| 25 | + |
| 26 | +// AliasMangler manages aliases for dials, dialsenv, dialsflag, and dialspflag |
| 27 | +// struct tags to make it possible to migrate from one name to another |
| 28 | +// conveniently. |
| 29 | +type AliasMangler struct{} |
| 30 | + |
| 31 | +// Mangle implements the Mangler interface. If an alias tag is defined, the |
| 32 | +// struct field will be copied with the non-aliased tag set to the alias's |
| 33 | +// value. |
| 34 | +func (a AliasMangler) Mangle(sf reflect.StructField) ([]reflect.StructField, error) { |
| 35 | + originalVals := map[string]string{} |
| 36 | + aliasVals := map[string]string{} |
| 37 | + |
| 38 | + sfTags, parseErr := structtag.Parse(string(sf.Tag)) |
| 39 | + if parseErr != nil { |
| 40 | + return nil, fmt.Errorf("error parsing source tags %w", parseErr) |
| 41 | + } |
| 42 | + |
| 43 | + anyAliasFound := false |
| 44 | + for _, tag := range aliasSourceTags { |
| 45 | + if originalVal, getErr := sfTags.Get(tag); getErr == nil { |
| 46 | + originalVals[tag] = originalVal.Name |
| 47 | + } |
| 48 | + |
| 49 | + if aliasVal, getErr := sfTags.Get(tag + dialsAliasTagSuffix); getErr == nil { |
| 50 | + aliasVals[tag] = aliasVal.Name |
| 51 | + anyAliasFound = true |
| 52 | + |
| 53 | + // remove the alias tag from the definition |
| 54 | + sfTags.Delete(tag + dialsAliasTagSuffix) |
| 55 | + } |
| 56 | + } |
| 57 | + |
| 58 | + if !anyAliasFound { |
| 59 | + // we didn't find any aliases so just get out early |
| 60 | + return []reflect.StructField{sf}, nil |
| 61 | + } |
| 62 | + |
| 63 | + aliasField := sf |
| 64 | + aliasField.Name += aliasFieldSuffix |
| 65 | + |
| 66 | + // now that we've copied it, reset the struct tags on the source field to |
| 67 | + // not include the alias tags |
| 68 | + sf.Tag = reflect.StructTag(sfTags.String()) |
| 69 | + |
| 70 | + tags, parseErr := structtag.Parse(string(aliasField.Tag)) |
| 71 | + if parseErr != nil { |
| 72 | + return nil, fmt.Errorf("error parsing struct tags: %w", parseErr) |
| 73 | + } |
| 74 | + |
| 75 | + for _, tag := range aliasSourceTags { |
| 76 | + // remove the alias tag so it's not left on the copied StructField |
| 77 | + tags.Delete(tag + dialsAliasTagSuffix) |
| 78 | + |
| 79 | + if aliasVals[tag] == "" { |
| 80 | + // if the particular flag isn't set at all just move on... |
| 81 | + continue |
| 82 | + } |
| 83 | + |
| 84 | + newDialsTag := &structtag.Tag{ |
| 85 | + Key: tag, |
| 86 | + Name: aliasVals[tag], |
| 87 | + } |
| 88 | + |
| 89 | + if setErr := tags.Set(newDialsTag); setErr != nil { |
| 90 | + return nil, fmt.Errorf("error setting new value for dials tag: %w", setErr) |
| 91 | + } |
| 92 | + |
| 93 | + // update dialsdesc if there is one |
| 94 | + if desc, getErr := tags.Get("dialsdesc"); getErr == nil { |
| 95 | + newDesc := &structtag.Tag{ |
| 96 | + Key: "dialsdesc", |
| 97 | + Name: desc.Name + " (alias of `" + originalVals[tag] + "`)", |
| 98 | + } |
| 99 | + if setErr := tags.Set(newDesc); setErr != nil { |
| 100 | + return nil, fmt.Errorf("error setting amended dialsdesc for tag %q: %w", tag, setErr) |
| 101 | + } |
| 102 | + } |
| 103 | + } |
| 104 | + |
| 105 | + // set the new flags on the alias field |
| 106 | + aliasField.Tag = reflect.StructTag(tags.String()) |
| 107 | + |
| 108 | + return []reflect.StructField{sf, aliasField}, nil |
| 109 | +} |
| 110 | + |
| 111 | +// Unmangle implements the Mangler interface and unwinds the alias copying |
| 112 | +// operation. Note that if both the source and alias are both set in the |
| 113 | +// configuration, an error will be returned. |
| 114 | +func (a AliasMangler) Unmangle(sf reflect.StructField, fvs []FieldValueTuple) (reflect.Value, error) { |
| 115 | + switch len(fvs) { |
| 116 | + case 1: |
| 117 | + // if there's only one tuple that means there was no alias, so just |
| 118 | + // return... |
| 119 | + return fvs[0].Value, nil |
| 120 | + case 2: |
| 121 | + // two means there's an alias so we should continue on... |
| 122 | + default: |
| 123 | + return reflect.Value{}, fmt.Errorf("expected 1 or 2 tuples, got %d", len(fvs)) |
| 124 | + } |
| 125 | + |
| 126 | + if !fvs[0].Value.IsNil() && !fvs[1].Value.IsNil() { |
| 127 | + return reflect.Value{}, fmt.Errorf("both alias and original set for field %q", sf.Name) |
| 128 | + } |
| 129 | + |
| 130 | + // return the first one that isn't nil |
| 131 | + for _, fv := range fvs { |
| 132 | + if !fv.Value.IsNil() { |
| 133 | + return fv.Value, nil |
| 134 | + } |
| 135 | + } |
| 136 | + |
| 137 | + // if we made it this far, they were both nil, which is fine -- just return |
| 138 | + // one of them. |
| 139 | + return fvs[0].Value, nil |
| 140 | +} |
| 141 | + |
| 142 | +// ShouldRecurse is called after Mangle for each field so nested struct |
| 143 | +// fields get iterated over after any transformation done by Mangle(). |
| 144 | +func (a AliasMangler) ShouldRecurse(_ reflect.StructField) bool { |
| 145 | + return true |
| 146 | +} |
0 commit comments