types/types.go (294 lines of code) (raw):

// Licensed to Elasticsearch B.V. under one or more contributor // license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright // ownership. Elasticsearch B.V. licenses this file to you under // the Apache License, Version 2.0 (the "License"); you may // not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. package types import ( "encoding/json" "fmt" "sort" "strings" "go.uber.org/zap" "k8s.io/apimachinery/pkg/runtime/schema" "sigs.k8s.io/controller-tools/pkg/markers" ) // Kind describes the kind of the type (alias, array, etc.) type Kind int const ( AliasKind Kind = iota BasicKind InterfaceKind MapKind PointerKind SliceKind StructKind UnknownKind UnsupportedKind ) func (k *Kind) UnmarshalJSON(b []byte) error { var s string if err := json.Unmarshal(b, &s); err != nil { return err } switch strings.ToUpper(s) { case "ALIAS": *k = AliasKind case "BASIC": *k = BasicKind case "INTERFACE": *k = InterfaceKind case "MAP": *k = MapKind case "POINTER": *k = PointerKind case "SLICE": *k = SliceKind case "STRUCT": *k = StructKind case "UNKNOWN": *k = UnknownKind default: return fmt.Errorf("unknown kind %s", s) } return nil } func (k Kind) MarshalJSON() ([]byte, error) { kindStr := "UNKNOWN" switch k { case AliasKind: kindStr = "ALIAS" case BasicKind: kindStr = "BASIC" case InterfaceKind: kindStr = "INTERFACE" case MapKind: kindStr = "MAP" case PointerKind: kindStr = "POINTER" case SliceKind: kindStr = "SLICE" case StructKind: kindStr = "STRUCT" } return json.Marshal(kindStr) } // Type describes a declared type type Type struct { UID string `json:"uid"` Name string `json:"name"` Package string `json:"package"` Doc string `json:"doc"` Default string `json:"default"` Validation []string `json:"validation"` Markers markers.MarkerValues `json:"markers"` GVK *schema.GroupVersionKind `json:"gvk"` Kind Kind `json:"kind"` Imported bool `json:"imported"` UnderlyingType *Type `json:"underlyingType"` // for aliases, slices and pointers KeyType *Type `json:"keyType"` // for maps ValueType *Type `json:"valueType"` // for maps Fields Fields `json:"fields"` // for structs References []*Type `json:"-"` // other types that refer to this type EnumValues []EnumValue `json:"enumValues"` // for enum values of aliased string types } func (t *Type) IsBasic() bool { switch t.Kind { case BasicKind: return true case SliceKind, PointerKind: return t.UnderlyingType != nil && t.UnderlyingType.IsBasic() case MapKind: return t.KeyType != nil && t.KeyType.IsBasic() && t.ValueType != nil && t.ValueType.IsBasic() case InterfaceKind: return true default: return false } } func (t *Type) Members() Fields { if t == nil { return nil } if len(t.Fields) > 0 { return t.Fields } switch t.Kind { case AliasKind, SliceKind, PointerKind: return t.UnderlyingType.Members() default: return nil } } func (t *Type) String() string { if t == nil { return "<unknown>" } var sb strings.Builder switch t.Kind { case MapKind: sb.WriteString("map[") sb.WriteString(t.KeyType.String()) sb.WriteString("]") sb.WriteString(t.ValueType.String()) return sb.String() case SliceKind: sb.WriteString("[]") case PointerKind: sb.WriteString("*") } if t.Package != "" { sb.WriteString(t.Package) sb.WriteString(".") } sb.WriteString(t.Name) return sb.String() } func (t *Type) IsAlias() bool { return t.Kind == AliasKind } func (t *Type) SortedReferences() []*Type { if t == nil || len(t.References) == 0 { return nil } sort.Slice(t.References, func(i, j int) bool { if t.References[i].Name < t.References[j].Name { return true } if t.References[i].Name == t.References[j].Name { return t.References[i].Package < t.References[j].Package } return false }) return t.References } func (t *Type) ContainsInlinedTypes() bool { for _, f := range t.Members() { if f.Inlined { return true } } return false } // TypeMap is a map of Type elements type TypeMap map[string]*Type // PropagateMarkers propagates markers to struct fields and certain types, from // their underlying types. func (types TypeMap) PropagateMarkers() { for _, t := range types { t.propagateMarkers() for _, f := range t.Fields { f.propagateMarkers() } } } func (types TypeMap) InlineTypes(propagateReference func(original *Type, additional *Type)) { // If C is inlined in B, and B is inlined in A; the fields of C are copied // into B before the fields of B is copied into A. The ideal order of // iterating and inlining fields is NOT known. Worst-case, only one type's // fields are inlined in its parent type in each iteration. maxDepth := 100 var numTypesToBeInlined int for iteration := 0; iteration < maxDepth; iteration++ { numTypesToBeInlined = 0 for _, t := range types { // By iterating backwards, it is safe to delete field at current index // and copy the fields of the inlined type. for i := len(t.Fields) - 1; i >= 0; i-- { if !t.Fields[i].Inlined { continue } numTypesToBeInlined += 1 embeddedType, ok := types[t.Fields[i].Type.UID] if !ok { zap.S().Warnw("Unable to find embedded type", "type", t, "embeddedType", t.Fields[i].Type) continue } // Only inline type's fields if the inlined type itself has no // types yet to be inlined. if !embeddedType.ContainsInlinedTypes() { zap.S().Debugw("Inlining embedded type", "type", t, "embeddedType", t.Fields[i].Type) t.Fields.inlineType(i, embeddedType) propagateReference(embeddedType, t) } } } if numTypesToBeInlined == 0 { return } } zap.S().Warnw("Failed to inline all inlined types", "remaining", numTypesToBeInlined) } func (t *Type) propagateMarkers() { if t.UnderlyingType == nil { return } switch t.Kind { case AliasKind, PointerKind: t.UnderlyingType.propagateMarkers() for name, marker := range t.UnderlyingType.Markers { if _, ok := t.Markers[name]; !ok { if t.Markers == nil { t.Markers = make(markers.MarkerValues) } t.Markers[name] = marker } } } } // Field describes a field in a struct. type Field struct { Name string Embedded bool // Embedded struct in Go typing Inlined bool // Inlined struct in serialization Doc string Default string Validation []string Markers markers.MarkerValues Type *Type } type Fields []*Field func (f *Field) propagateMarkers() { f.Type.propagateMarkers() for name, marker := range f.Type.Markers { if _, ok := f.Markers[name]; !ok { if f.Markers == nil { f.Markers = make(markers.MarkerValues) } f.Markers[name] = marker } } } // inlineType replaces field at index i with the fields of inlined type. func (fields *Fields) inlineType(i int, inlined *Type) { new := make([]*Field, 0, len(*fields)+len(inlined.Fields)-1) new = append(new, (*fields)[:i]...) new = append(new, inlined.Fields...) *fields = append(new, (*fields)[i+1:]...) } // Identifier generates the non-unique identifier for the type, disregarding it // being a e.g. a list or pointer, and will thus be the same for both *Foo and // Foo. Use Type's UID for a unique identifier. func Identifier(t *Type) string { if t.Package == "" { return t.Name } return fmt.Sprintf("%s.%s", t.Package, t.Name) } // GroupVersionDetails encapsulates details about a discovered API group. type GroupVersionDetails struct { schema.GroupVersion Doc string Kinds []string Types TypeMap Markers markers.MarkerValues } func (gvd GroupVersionDetails) GroupVersionString() string { return gvd.GroupVersion.String() } func (gvd GroupVersionDetails) TypeForKind(k string) *Type { return gvd.Types[k] } func (gvd GroupVersionDetails) SortedTypes() []*Type { typeList := make([]*Type, len(gvd.Types)) i := 0 for _, t := range gvd.Types { typeList[i] = t i++ } sort.Slice(typeList, func(i, j int) bool { return typeList[i].Name < typeList[j].Name }) return typeList } func (gvd GroupVersionDetails) SortedKinds() []string { if len(gvd.Kinds) <= 1 { return gvd.Kinds } kindsList := make([]string, len(gvd.Kinds)) copy(kindsList, gvd.Kinds) sort.Strings(kindsList) return kindsList } // EnumValue describes a constant value for enumerations type EnumValue struct { Name string Doc string }