func (f *Flattener) FlattenSchema(baseSchema apiext.JSONSchemaProps, currentPackage *loader.Package) *apiext.JSONSchemaProps {
	resSchema := baseSchema.DeepCopy()
	EditSchema(resSchema, &flattenVisitor{
		Flattener:      f,
		currentPackage: currentPackage,
	})
	return resSchema
}
FlattenSchema flattens the given schema, removing any references.
It deep-copies the schema first, so the input schema won't be affected.
package crd
import (
	"fmt"
	"reflect"
	"sort"
	"strings"
	"sync"
	apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
	"sigs.k8s.io/controller-tools/pkg/loader"
)
type ErrorRecorder interface {
	AddError(error)
}
func isOrNil(val reflect.Value, valInt interface{}, zeroInt interface{}) bool {
	switch valKind := val.Kind(); valKind {
	case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
		return val.IsNil()
	default:
		return valInt == zeroInt
	}
}
func flattenAllOfInto(dst *apiext.JSONSchemaProps, src apiext.JSONSchemaProps, errRec ErrorRecorder) {
	if len(src.AllOf) > 0 {
		for _, embedded := range src.AllOf {
			flattenAllOfInto(dst, embedded, errRec)
		}
	}
	dstVal := reflect.Indirect(reflect.ValueOf(dst))
	srcVal := reflect.ValueOf(src)
	typ := dstVal.Type()
	srcRemainder := apiext.JSONSchemaProps{}
	srcRemVal := reflect.Indirect(reflect.ValueOf(&srcRemainder))
	dstRemainder := apiext.JSONSchemaProps{}
	dstRemVal := reflect.Indirect(reflect.ValueOf(&dstRemainder))
	hoisted := false
	for i := 0; i < srcVal.NumField(); i++ {
		fieldName := typ.Field(i).Name
		switch fieldName {
		case "AllOf":
			continue
		case "Title", "Description", "Example", "ExternalDocs":
			continue
		}
		srcField := srcVal.Field(i)
		fldTyp := srcField.Type()
		zeroVal := reflect.Zero(fldTyp)
		zeroInt := zeroVal.Interface()
		srcInt := srcField.Interface()
		if isOrNil(srcField, srcInt, zeroInt) {
			continue
		}
		dstField := dstVal.Field(i)
		dstInt := dstField.Interface()
		if isOrNil(dstField, dstInt, zeroInt) {
			dstField.Set(srcField)
			continue
		}
		if fldTyp.Comparable() && srcInt == dstInt {
			continue
		}
		switch fieldName {
		case "Properties":
			srcMap := srcInt.(map[string]apiext.JSONSchemaProps)
			dstMap := dstInt.(map[string]apiext.JSONSchemaProps)
			for k, v := range srcMap {
				dstProp, exists := dstMap[k]
				if !exists {
					dstMap[k] = v
					continue
				}
				flattenAllOfInto(&dstProp, v, errRec)
				dstMap[k] = dstProp
			}
		case "Required":
			dstField.Set(reflect.AppendSlice(dstField, srcField))
		case "Type":
			if srcInt != dstInt {
				errRec.AddError(fmt.Errorf("conflicting types in allOf branches in schema: %s vs %s", dstInt, srcInt))
			}
		case "AdditionalProperties":
			srcProps := srcInt.(*apiext.JSONSchemaPropsOrBool)
			if srcProps.Schema == nil {
				continue
			}
			dstProps := dstInt.(*apiext.JSONSchemaPropsOrBool)
			if dstProps.Schema == nil {
				dstProps.Schema = &apiext.JSONSchemaProps{}
			}
			flattenAllOfInto(dstProps.Schema, *srcProps.Schema, errRec)
		default:
			hoisted = true
			srcRemVal.Field(i).Set(srcField)
			dstRemVal.Field(i).Set(dstField)
			dstField.Set(zeroVal)
		}
	}
	if hoisted {
		dst.AllOf = append(dst.AllOf, dstRemainder, srcRemainder)
	}
	if len(dst.Required) > 0 {
		reqUniq := make(map[string]struct{})
		for _, req := range dst.Required {
			reqUniq[req] = struct{}{}
		}
		dst.Required = make([]string, 0, len(reqUniq))
		for req := range reqUniq {
			dst.Required = append(dst.Required, req)
		}
		sort.Strings(dst.Required)
	}
}
type allOfVisitor struct {
	errRec ErrorRecorder
}
func (v *allOfVisitor) Visit(schema *apiext.JSONSchemaProps) SchemaVisitor {
	if schema == nil {
		return v
	}
	origAllOf := schema.AllOf
	schema.AllOf = nil
	for _, embedded := range origAllOf {
		flattenAllOfInto(schema, embedded, v.errRec)
	}
	return v
}
func FlattenEmbedded(schema *apiext.JSONSchemaProps, errRec ErrorRecorder) *apiext.JSONSchemaProps {
	outSchema := schema.DeepCopy()
	EditSchema(outSchema, &allOfVisitor{errRec: errRec})
	return outSchema
}
type Flattener struct {
	Parser *Parser
	LookupReference func(ref string, contextPkg *loader.Package) (TypeIdent, error)
	flattenedTypes map[TypeIdent]apiext.JSONSchemaProps
	initOnce       sync.Once
}
func (f *Flattener) init() {
	f.initOnce.Do(func() {
		f.flattenedTypes = make(map[TypeIdent]apiext.JSONSchemaProps)
		if f.LookupReference == nil {
			f.LookupReference = identFromRef
		}
	})
}
func (f *Flattener) cacheType(typ TypeIdent, schema apiext.JSONSchemaProps) {
	f.init()
	f.flattenedTypes[typ] = schema
}
func (f *Flattener) loadUnflattenedSchema(typ TypeIdent) (*apiext.JSONSchemaProps, error) {
	f.Parser.NeedSchemaFor(typ)
	baseSchema, found := f.Parser.Schemata[typ]
	if !found {
		return nil, fmt.Errorf("unable to locate schema for type %s", typ)
	}
	return &baseSchema, nil
}
func (f *Flattener) FlattenType(typ TypeIdent) *apiext.JSONSchemaProps {
	f.init()
	if cachedSchema, isCached := f.flattenedTypes[typ]; isCached {
		return &cachedSchema
	}
	baseSchema, err := f.loadUnflattenedSchema(typ)
	if err != nil {
		typ.Package.AddError(err)
		return nil
	}
	resSchema := f.FlattenSchema(*baseSchema, typ.Package)
	f.cacheType(typ, *resSchema)
	return resSchema
}