package depinject import ( "reflect" "strings" "unicode" "github.com/pkg/errors" "golang.org/x/exp/slices" ) // isExportedType checks if the type is exported and not in an internal // package. NOTE: generic type parameters are not checked because this // would involve complex parsing of type names (there is no reflect API for // generic type parameters). Parsing of these parameters should be possible // if someone chooses to do it in the future, but care should be taken to // be exhaustive and cover all cases like pointers, map's, chan's, etc. which // means you actually need a real parser and not just a regex. func isExportedType(typ reflect.Type) error { name := typ.Name() pkgPath := typ.PkgPath() if name != "" && pkgPath != "" { if unicode.IsLower([]rune(name)[0]) { return errors.Errorf("type must be exported: %s", typ) } pkgParts := strings.Split(pkgPath, "/") if slices.Contains(pkgParts, "internal") { return errors.Errorf("type must not come from an internal package: %s", typ) } return nil } switch typ.Kind() { case reflect.Array, reflect.Slice, reflect.Chan, reflect.Pointer: return isExportedType(typ.Elem()) case reflect.Func: numIn := typ.NumIn() for i := 0; i < numIn; i++ { err := isExportedType(typ.In(i)) if err != nil { return err } } numOut := typ.NumOut() for i := 0; i < numOut; i++ { err := isExportedType(typ.Out(i)) if err != nil { return err } } return nil case reflect.Map: err := isExportedType(typ.Key()) if err != nil { return err } return isExportedType(typ.Elem()) default: // all the remaining types are builtin, non-composite types (like integers), so they are fine to use return nil } }