github.com/octohelm/wagon@v0.0.0-20240308040401-88662650dc0b/pkg/engine/plan/internal/cueify.go (about) 1 package internal 2 3 import ( 4 "bytes" 5 "encoding" 6 "fmt" 7 "go/ast" 8 "reflect" 9 "strings" 10 ) 11 12 type OneOfType interface { 13 OneOf() []any 14 } 15 16 var oneOfType = reflect.TypeOf((*OneOfType)(nil)).Elem() 17 var textMarshalerType = reflect.TypeOf((*encoding.TextMarshaler)(nil)).Elem() 18 19 func newConvert(r TaskRegister) *convert { 20 return &convert{ 21 r: r, 22 defs: map[reflect.Type]bool{}, 23 } 24 } 25 26 type convert struct { 27 r TaskRegister 28 defs map[reflect.Type]bool 29 } 30 31 type opt struct { 32 naming string 33 extra string 34 } 35 36 func (c *convert) toCueType(tpe reflect.Type, o opt) []byte { 37 if o.naming == "" && tpe.PkgPath() != "" { 38 if _, ok := c.defs[tpe]; !ok { 39 c.defs[tpe] = true 40 c.r.Register(reflect.New(tpe).Interface()) 41 } 42 43 if o.extra != "" { 44 return []byte(fmt.Sprintf(`#%s & { 45 %s 46 }`, tpe.Name(), o.extra)) 47 } 48 return []byte(fmt.Sprintf("#%s", tpe.Name())) 49 } 50 51 if tpe.Implements(textMarshalerType) { 52 return []byte("string") 53 } 54 55 if tpe.Implements(oneOfType) { 56 if ot, ok := reflect.New(tpe).Interface().(OneOfType); ok { 57 types := ot.OneOf() 58 b := bytes.NewBuffer(nil) 59 60 for i := range types { 61 t := reflect.TypeOf(types[i]) 62 if t.Kind() == reflect.Ptr { 63 t = t.Elem() 64 } 65 if i > 0 { 66 b.WriteString(" | ") 67 } 68 b.Write(c.toCueType(t, opt{extra: o.extra})) 69 } 70 71 return b.Bytes() 72 } 73 } 74 75 switch tpe.Kind() { 76 case reflect.Ptr: 77 return []byte(fmt.Sprintf("%s | null", c.toCueType(tpe.Elem(), opt{extra: o.extra}))) 78 case reflect.Map: 79 return []byte(fmt.Sprintf("[X=%s]: %s", c.toCueType(tpe.Key(), opt{extra: o.extra}), c.toCueType(tpe.Elem(), opt{extra: o.extra}))) 80 case reflect.Slice: 81 return []byte(fmt.Sprintf("[...%s]", c.toCueType(tpe.Elem(), opt{extra: o.extra}))) 82 case reflect.Struct: 83 b := bytes.NewBuffer(nil) 84 85 _, _ = fmt.Fprintf(b, `{ 86 `) 87 88 walkFields(tpe, func(i *fieldInfo) { 89 t := i.tpe 90 91 // FIXME may support other inline 92 if i.inline { 93 if t.Kind() == reflect.Map { 94 _, _ = fmt.Fprintf(b, `[!~"\\$wagon"]: %s`, c.toCueType(t.Elem(), opt{ 95 extra: i.cueExtra, 96 })) 97 } 98 return 99 } 100 101 if i.optional { 102 if t.Kind() == reflect.Ptr { 103 t = t.Elem() 104 } 105 _, _ = fmt.Fprintf(b, "%s?: ", i.name) 106 } else { 107 _, _ = fmt.Fprintf(b, "%s: ", i.name) 108 } 109 110 cueType := c.toCueType(t, opt{ 111 extra: i.cueExtra, 112 }) 113 114 if len(i.enum) > 0 { 115 for i, e := range i.enum { 116 if i > 0 { 117 _, _ = fmt.Fprint(b, " | ") 118 } 119 _, _ = fmt.Fprintf(b, `%q`, e) 120 } 121 } else { 122 _, _ = fmt.Fprintf(b, "%s", cueType) 123 } 124 125 if i.defaultValue != nil { 126 switch string(cueType) { 127 case "[]byte": 128 _, _ = fmt.Fprintf(b, ` | *'%s'`, *i.defaultValue) 129 case "string": 130 _, _ = fmt.Fprintf(b, ` | *%q`, *i.defaultValue) 131 default: 132 _, _ = fmt.Fprintf(b, ` | *%v`, *i.defaultValue) 133 } 134 } 135 136 if len(i.attrs) > 0 { 137 _, _ = fmt.Fprintf(b, " @wagon(%s)", strings.Join(i.attrs, ",")) 138 } 139 140 _, _ = fmt.Fprint(b, "\n") 141 }) 142 143 _, _ = fmt.Fprintf(b, `}`) 144 145 return b.Bytes() 146 case reflect.Interface: 147 return []byte("_") 148 default: 149 return []byte(tpe.Kind().String()) 150 } 151 } 152 153 type fieldInfo struct { 154 name string 155 cueExtra string 156 idx int 157 tpe reflect.Type 158 optional bool 159 inline bool 160 defaultValue *string 161 enum []string 162 attrs []string 163 } 164 165 func (i *fieldInfo) EmptyDefaults() (string, bool) { 166 if i.tpe.PkgPath() != "" { 167 return "", false 168 } 169 170 switch i.tpe.Kind() { 171 case reflect.Slice: 172 return "", false 173 case reflect.Map: 174 return "", false 175 case reflect.Interface: 176 return "", false 177 } 178 return fmt.Sprintf("%v", reflect.New(i.tpe).Elem()), true 179 } 180 181 func (i *fieldInfo) HasAttr(expectAttr string) bool { 182 for _, attr := range i.attrs { 183 if attr == expectAttr { 184 return true 185 } 186 } 187 return false 188 } 189 190 func walkFields(s reflect.Type, each func(info *fieldInfo)) { 191 for i := 0; i < s.NumField(); i++ { 192 f := s.Field(i) 193 194 if !ast.IsExported(f.Name) { 195 continue 196 } 197 198 info := &fieldInfo{} 199 info.idx = i 200 info.name = f.Name 201 info.tpe = f.Type 202 203 jsonTag, hasJsonTag := f.Tag.Lookup("json") 204 if !hasJsonTag { 205 if f.Anonymous && f.Type.Kind() == reflect.Struct { 206 walkFields(f.Type, each) 207 } 208 continue 209 } 210 211 if strings.Contains(jsonTag, ",omitempty") { 212 info.optional = true 213 } 214 215 if strings.Contains(jsonTag, ",inline") { 216 info.inline = true 217 info.name = "" 218 } 219 220 if cueExtra, hasCueExtra := f.Tag.Lookup("cueExtra"); hasCueExtra { 221 info.cueExtra = cueExtra 222 } 223 224 wagonTag, hasWagonTag := f.Tag.Lookup("wagon") 225 if jsonTag == "-" && !hasWagonTag { 226 continue 227 } 228 229 if jsonName := strings.SplitN(jsonTag, ",", 2)[0]; jsonName != "" { 230 info.name = jsonName 231 } 232 233 if hasWagonTag { 234 attrs := strings.Split(wagonTag, ",") 235 236 for _, n := range attrs { 237 parts := strings.SplitN(n, "=", 2) 238 if len(parts) == 2 && parts[0] == "name" { 239 info.name = parts[1] 240 continue 241 } 242 info.attrs = append(info.attrs, n) 243 } 244 } 245 246 if defaultValue, ok := f.Tag.Lookup("default"); ok { 247 info.defaultValue = &defaultValue 248 } 249 250 if enumValue, ok := f.Tag.Lookup("enum"); ok { 251 info.enum = strings.Split(enumValue, ",") 252 } 253 254 each(info) 255 } 256 }