<commit_before>package objectvisitor

import (
	"context"
	"sync"

	"go.opencensus.io/trace"
	"golang.org/x/sync/errgroup"

	"github.com/heptio/developer-dash/internal/gvk"
	"github.com/heptio/developer-dash/internal/queryer"
	"github.com/pkg/errors"
	corev1 "k8s.io/api/core/v1"
	extv1beta1 "k8s.io/api/extensions/v1beta1"
	"k8s.io/apimachinery/pkg/api/meta"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apimachinery/pkg/types"
)

// ClusterObject is a cluster object.
// NOTE: this might not be the most succinct description.
type ClusterObject interface {
	runtime.Object
}

// ObjectHandler performs actions on an object. Can be used to augment
// visitor actions with extra functionality.
type ObjectHandler interface {
	AddChild(parent ClusterObject, children ...ClusterObject) error
	Process(ctx context.Context, object ClusterObject) error
}

// Visitor is a visitor for cluster objects. It will visit an object and all of
// its ancestors and descendants.
type Visitor interface {
	Visit(ctx context.Context, object ClusterObject) error
}

// ObjectHandlerFactory creates ObjectHandler given a ClusterObject.
type ObjectHandlerFactory func(ClusterObject) (ObjectHandler, error)

// DefaultFactoryGenerator generates ObjectHandlerFactory based on GVK.
type DefaultFactoryGenerator struct {
	m map[schema.GroupVersionKind]ObjectHandlerFactory
}

// NewDefaultFactoryGenerator creates an instance of NewDefaultFactoryGenerator.
func NewDefaultFactoryGenerator() *DefaultFactoryGenerator {
	return &DefaultFactoryGenerator{
		m: make(map[schema.GroupVersionKind]ObjectHandlerFactory),
	}
}

// Register registers an ObjectHandlerFactory for a GVK.
func (dfg *DefaultFactoryGenerator) Register(gvk schema.GroupVersionKind, fn ObjectHandlerFactory) error {
	if _, ok := dfg.m[gvk]; ok {
		return errors.Errorf("%s has already been registered", gvk)
	}

	dfg.m[gvk] = fn

	return nil
}

// FactoryFunc creates an ObjectHandlerFactory for a GVK.
func (dfg *DefaultFactoryGenerator) FactoryFunc() ObjectHandlerFactory {
	return func(object ClusterObject) (ObjectHandler, error) {
		if object == nil {
			return nil, errors.New("unable to find factory for nil object")
		}

		objectKind := object.GetObjectKind()
		if objectKind == nil {
			return nil, errors.New("object kind is nil")
		}

		gvk := objectKind.GroupVersionKind()
		factory, ok := dfg.m[gvk]
		if !ok {
			return nil, errors.Errorf("%s was not registered",
				gvk)
		}

		return factory(object)
	}
}

// DefaultVisitor is the default implementation of Visitor.
type DefaultVisitor struct {
	queryer        queryer.Queryer
	handlerFactory ObjectHandlerFactory
	visited        map[types.UID]bool
	visitedMu      sync.Mutex
}

var _ Visitor = (*DefaultVisitor)(nil)

// NewDefaultVisitor creates an instance of DefaultVisitor.
func NewDefaultVisitor(q queryer.Queryer, factory ObjectHandlerFactory) (*DefaultVisitor, error) {
	if factory == nil {
		return nil, errors.Errorf("factory was nil")
	}

	return &DefaultVisitor{
		queryer:        q,
		handlerFactory: factory,
		visited:        make(map[types.UID]bool),
	}, nil
}

// hasVisited returns true if this object has already been visited. If the
// object has not been visited, it returns false, and sets the object
// visit status to true.
func (dv *DefaultVisitor) hasVisited(object runtime.Object) (bool, error) {
	dv.visitedMu.Lock()
	defer dv.visitedMu.Unlock()

	accessor := meta.NewAccessor()
	uid, err := accessor.UID(object)
	if err != nil {
		return false, errors.Wrap(err, "get uid from object")
	}

	if _, ok := dv.visited[uid]; ok {
		return true, nil
	}

	dv.visited[uid] = true

	return false, nil
}

// Visit visits a ClusterObject.
func (dv *DefaultVisitor) Visit(ctx context.Context, object ClusterObject) error {
	if object == nil {
		return errors.New("trying to visit a nil object")
	}

	hasVisited, err := dv.hasVisited(object)
	if err != nil {
		return errors.Wrap(err, "check for visit object")
	}
	if hasVisited {
		return nil
	}

	// Create a handler factory for this object. This allows the visitor's caller to
	// interact with the ancestors and descendants of the object.
	o, err := dv.handlerFactory(object)
	if err != nil {
		return err
	}

	return dv.visitObject(ctx, object, o)<commit_msg>Recover from panic if unstructured becomes nil<commit_after>package objectvisitor

import (
	"context"
	"sync"

	"go.opencensus.io/trace"
	"golang.org/x/sync/errgroup"

	"github.com/heptio/developer-dash/internal/gvk"
	"github.com/heptio/developer-dash/internal/queryer"
	"github.com/pkg/errors"
	corev1 "k8s.io/api/core/v1"
	extv1beta1 "k8s.io/api/extensions/v1beta1"
	"k8s.io/apimachinery/pkg/api/meta"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apimachinery/pkg/types"
)

// ClusterObject is a cluster object.
// NOTE: this might not be the most succinct description.
type ClusterObject interface {
	runtime.Object
}

// ObjectHandler performs actions on an object. Can be used to augment
// visitor actions with extra functionality.
type ObjectHandler interface {
	AddChild(parent ClusterObject, children ...ClusterObject) error
	Process(ctx context.Context, object ClusterObject) error
}

// Visitor is a visitor for cluster objects. It will visit an object and all of
// its ancestors and descendants.
type Visitor interface {
	Visit(ctx context.Context, object ClusterObject) error
}

// ObjectHandlerFactory creates ObjectHandler given a ClusterObject.
type ObjectHandlerFactory func(ClusterObject) (ObjectHandler, error)

// DefaultFactoryGenerator generates ObjectHandlerFactory based on GVK.
type DefaultFactoryGenerator struct {
	m map[schema.GroupVersionKind]ObjectHandlerFactory
}

// NewDefaultFactoryGenerator creates an instance of NewDefaultFactoryGenerator.
func NewDefaultFactoryGenerator() *DefaultFactoryGenerator {
	return &DefaultFactoryGenerator{
		m: make(map[schema.GroupVersionKind]ObjectHandlerFactory),
	}
}

// Register registers an ObjectHandlerFactory for a GVK.
func (dfg *DefaultFactoryGenerator) Register(gvk schema.GroupVersionKind, fn ObjectHandlerFactory) error {
	if _, ok := dfg.m[gvk]; ok {
		return errors.Errorf("%s has already been registered", gvk)
	}

	dfg.m[gvk] = fn

	return nil
}

// FactoryFunc creates an ObjectHandlerFactory for a GVK.
func (dfg *DefaultFactoryGenerator) FactoryFunc() ObjectHandlerFactory {
	return func(object ClusterObject) (ObjectHandler, error) {
		if object == nil {
			return nil, errors.New("unable to find factory for nil object")
		}

		objectKind := object.GetObjectKind()
		if objectKind == nil {
			return nil, errors.New("object kind is nil")
		}

		gvk := objectKind.GroupVersionKind()
		factory, ok := dfg.m[gvk]
		if !ok {
			return nil, errors.Errorf("%s was not registered",
				gvk)
		}

		return factory(object)
	}
}

// DefaultVisitor is the default implementation of Visitor.
type DefaultVisitor struct {
	queryer        queryer.Queryer
	handlerFactory ObjectHandlerFactory
	visited        map[types.UID]bool
	visitedMu      sync.Mutex
}

var _ Visitor = (*DefaultVisitor)(nil)

// NewDefaultVisitor creates an instance of DefaultVisitor.
func NewDefaultVisitor(q queryer.Queryer, factory ObjectHandlerFactory) (*DefaultVisitor, error) {
	if factory == nil {
		return nil, errors.Errorf("factory was nil")
	}

	return &DefaultVisitor{
		queryer:        q,
		handlerFactory: factory,
		visited:        make(map[types.UID]bool),
	}, nil
}

// hasVisited returns true if this object has already been visited. If the
// object has not been visited, it returns false, and sets the object
// visit status to true.
func (dv *DefaultVisitor) hasVisited(object runtime.Object) (bool, error) {
	dv.visitedMu.Lock()
	defer dv.visitedMu.Unlock()

	accessor := meta.NewAccessor()
	uid, err := accessor.UID(object)
	if err != nil {
		return false, errors.Wrap(err, "get uid from object")
	}

	if _, ok := dv.visited[uid]; ok {
		return true, nil
	}

	dv.visited[uid] = true

	return false, nil
}

// Visit visits a ClusterObject.
func (dv *DefaultVisitor) Visit(ctx context.Context, object ClusterObject) error {
	if object == nil {
		return errors.New("trying to visit a nil object")
	}

	objectCopy := object.DeepCopyObject()
	hasVisited, err := dv.hasVisited(objectCopy)
	if err != nil {
		return errors.Wrap(err, "check for visit object")
	}
	if hasVisited {
		return nil
	}

	// Create a handler factory for this object. This allows the visitor's caller to
	// interact with the ancestors and descendants of the object.
	o, err := dv.handlerFactory(objectCopy)
	if err != nil {
		return err
	}

	return dv.visitObject(ctx, objectCopy, o)
{'fraction_non_alphanumeric': 0.10111974405850091, 'fraction_numerical': 0.0031992687385740404, 'mean_word_length': 4.304848484848485, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '364101', 'n_tokens_mistral': 2840, 'n_tokens_neox': 2689, 'n_words': 811}