github.com/greenpau/go-authcrunch@v1.1.4/internal/tag/tag.go (about) 1 // Copyright 2022 Paul Greenberg greenpau@outlook.com 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package tag 16 17 import ( 18 "fmt" 19 "github.com/iancoleman/strcase" 20 "reflect" 21 "strings" 22 "unicode" 23 ) 24 25 // Options stores compliance check options. 26 type Options struct { 27 Disabled bool 28 DisableTagPresent bool 29 DisableTagMismatch bool 30 DisableTagOnEmpty bool 31 AllowFieldMismatch bool 32 AllowedFields map[string]interface{} 33 } 34 35 // GetTagCompliance performs struct tag compliance checks. 36 func GetTagCompliance(resource interface{}, opts *Options) ([]string, error) { 37 var output []string 38 if opts == nil { 39 opts = &Options{} 40 } 41 42 if opts.Disabled { 43 return output, nil 44 } 45 46 rr := reflect.TypeOf(resource).Elem() 47 //resourceType := fmt.Sprintf("%s", rr.Name()) 48 rk := fmt.Sprintf("%s", rr.Kind()) 49 50 if rk != "struct" { 51 return nil, fmt.Errorf("resource kind %q is unsupported", rk) 52 } 53 54 suggestedStructChanges := []string{} 55 56 requiredTags := []string{"json", "xml", "yaml"} 57 for i := 0; i < rr.NumField(); i++ { 58 resourceField := rr.Field(i) 59 if !unicode.IsUpper(rune(resourceField.Name[0])) { 60 // Skip internal fields. 61 continue 62 } 63 64 expTagValue := convertFieldToTag(resourceField.Name) 65 if !opts.DisableTagOnEmpty { 66 expTagValue = expTagValue + ",omitempty" 67 } 68 var lastTag bool 69 for j, tagName := range requiredTags { 70 if len(requiredTags)-1 == j { 71 lastTag = true 72 } 73 74 tagValue := resourceField.Tag.Get(tagName) 75 76 if tagValue == "-" { 77 break 78 } 79 if tagValue == "" && !opts.DisableTagPresent { 80 output = append(output, fmt.Sprintf( 81 "tag %q not found in %s.%s (%v)", 82 tagName, 83 //resourceType, 84 rr.Name(), 85 resourceField.Name, 86 resourceField.Type, 87 )) 88 if lastTag { 89 tags := makeTags(requiredTags, expTagValue) 90 resType := fmt.Sprintf("%v", resourceField.Type) 91 resType = strings.Replace(resType, "identity.", "", -1) 92 suggestedStructChanges = append(suggestedStructChanges, fmt.Sprintf( 93 "%s %s %s", resourceField.Name, resType, tags, 94 )) 95 } 96 continue 97 } 98 //if strings.Contains(tagValue, ",omitempty") { 99 // tagValue = strings.Replace(tagValue, ",omitempty", "", -1) 100 //} 101 if (tagValue != expTagValue) && !opts.DisableTagMismatch { 102 if opts.AllowFieldMismatch && opts.AllowedFields != nil { 103 fieldName := strings.Split(tagValue, ",")[0] 104 if _, exists := opts.AllowedFields[fieldName]; exists { 105 continue 106 } 107 } 108 output = append(output, fmt.Sprintf( 109 "tag %q mismatch found in %s.%s (%v): %s (actual) vs. %s (expected)", 110 tagName, 111 //resourceType, 112 rr.Name(), 113 resourceField.Name, 114 resourceField.Type, 115 tagValue, 116 expTagValue, 117 )) 118 continue 119 } 120 } 121 } 122 123 if len(suggestedStructChanges) > 0 { 124 output = append(output, fmt.Sprintf( 125 "suggested struct changes to %s:\n%s", 126 rr.Name(), 127 strings.Join(suggestedStructChanges, "\n"), 128 )) 129 } 130 131 if len(output) > 0 { 132 return output, fmt.Errorf("struct %q is not compliant", rr.Name()) 133 } 134 135 return output, nil 136 } 137 138 func convertFieldToTag(s string) string { 139 s = strcase.ToSnake(s) 140 s = strings.ReplaceAll(s, "_md_5", "_md5") 141 s = strings.ReplaceAll(s, "open_ssh", "openssh") 142 s = strings.ReplaceAll(s, "o_auth_2", "oauth2") 143 s = strings.ReplaceAll(s, "_ur_ls", "_urls") 144 return s 145 } 146 147 func makeTags(tags []string, s string) string { 148 var b strings.Builder 149 b.WriteRune('`') 150 tagOutput := []string{} 151 for _, tag := range tags { 152 tagOutput = append(tagOutput, tag+":\""+s+"\"") 153 } 154 b.WriteString(strings.Join(tagOutput, " ")) 155 b.WriteRune('`') 156 return b.String() 157 }