grafana/pkg/framework/coremodel/registry.go
sam boyer a3402641d6
api: Validate dashboards on save via coremodels, behind feature toggle (#48252)
* Add coremodelValidation feature flag

* coremodels: use stubs when feature flag is off

* api: validate dashboards on save

* Need pointer receiver for FeatureManager

* Update dashboard Go model

* Align doc comments

* Include CoremodelRegistry in test

* Wedge coremodel in on all test cases, ugh

* Ugh fix comment again

* Update pkg/framework/coremodel/staticregistry/provide.go

Co-authored-by: Artur Wierzbicki <wierzbicki.artur.94@gmail.com>

* Update Thema (and its deps) for better errs

* omg whitespace

Co-authored-by: Artur Wierzbicki <wierzbicki.artur.94@gmail.com>
2022-05-22 02:44:12 +02:00

88 lines
2.1 KiB
Go

package coremodel
import (
"errors"
"fmt"
"sync"
"github.com/grafana/thema"
)
var (
// ErrModelAlreadyRegistered is returned when trying to register duplicate model to Registry.
ErrModelAlreadyRegistered = errors.New("error registering duplicate model")
)
// Registry is a registry of coremodel instances.
type Registry struct {
lock sync.RWMutex
models []Interface
modelIdx map[string]Interface
}
// NewRegistry returns a new Registry with the provided coremodel instances.
func NewRegistry(models ...Interface) (*Registry, error) {
r := &Registry{
models: make([]Interface, 0, len(models)),
modelIdx: make(map[string]Interface, len(models)),
}
if err := r.addModels(models); err != nil {
return nil, err
}
return r, nil
}
// Register adds coremodels to the Registry.
func (r *Registry) Register(models ...Interface) error {
return r.addModels(models)
}
// List returns all coremodels registered in this Registry.
func (r *Registry) List() []Interface {
r.lock.RLock()
defer r.lock.RUnlock()
return r.models
}
func (r *Registry) addModels(models []Interface) error {
r.lock.Lock()
defer r.lock.Unlock()
// Update model index and return an error if trying to register a duplicate.
for _, m := range models {
k := m.Lineage().Name()
// Ensure assignability first. TODO will this blow up for dashboards?
if err := thema.AssignableTo(m.CurrentSchema(), m.GoType()); err != nil {
return fmt.Errorf("%s schema version %v not assignable to provided Go type: %w", k, m.CurrentSchema().Version(), err)
}
if _, ok := r.modelIdx[k]; ok {
return ErrModelAlreadyRegistered
}
r.modelIdx[k] = m
}
// Remake model list.
// TODO: this can be more performant (proper resizing, maybe single loop with index building, etc.).
r.models = r.models[:0]
for _, m := range r.modelIdx {
r.models = append(r.models, m)
}
return nil
}
// Get retrieves a coremodel with the given string identifier. nil, false
// is returned if no such coremodel exists.
func (r *Registry) Get(name string) (cm Interface, has bool) {
r.lock.RLock()
cm, has = r.modelIdx[name]
r.lock.RUnlock()
return
}