Skip to content

Commit

Permalink
stash
Browse files Browse the repository at this point in the history
  • Loading branch information
lukasjarosch committed Feb 23, 2024
1 parent 7a27781 commit 927977c
Show file tree
Hide file tree
Showing 23 changed files with 2,758 additions and 800 deletions.
13 changes: 7 additions & 6 deletions .mockery.yaml
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
with-expecter: True
dir: "mocks/{{.PackageName}}"
filename: "{{ .InterfaceNameSnake }}.go"
dir: "mocks/"
packages:
github.com/lukasjarosch/skipper/data:
github.com/lukasjarosch/skipper:
interfaces:
File:
FileCodec:

Codec:
DataContainer:
ReferenceValueSource:
ReferenceSourceGetter:
ReferenceSourceRelativeGetter:
6 changes: 3 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@ RESET := $(shell tput -Txterm sgr0)
test: ## Run all tests
$(GOTEST) -race -v $(GOLIST)

coverage: ## Run tests with coverage and export it into 'profile.cov'.
coverage: ## Run tests with coverage and export it into 'profile.cov'.
$(GOTEST) -cover -covermode=count -coverprofile=$(COVERAGE_FILE) ./...
$(GO) tool cover -func $(COVERAGE_FILE)

show-coverage: coverage ## Run coverage and open the rendered coverage site in the browser
show-coverage: coverage ## Run coverage and open the rendered coverage site in the browser
$(GO) tool cover -html=$(COVERAGE_FILE)
echo -e "\n=> Coverage report opened in your default browser"
@echo -e "\n=> Coverage report opened in your default browser"

## Lint

Expand Down
73 changes: 64 additions & 9 deletions class.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,39 @@ type (
ClassIdentifier = data.Path
)

type DataGetter interface {
GetPath(data.Path) (data.Value, error)
}

type DataSetter interface {
SetPath(data.Path, interface{}) error
}

type DataSetterGetter interface {
DataGetter
DataSetter
}

type DataWalker interface {
Walk(func(data.Path, data.Value, bool) error) error
}

type AbsolutePathMaker interface {
// AbsolutePath resolves the given path to an absolute path, within the given context.
// The context is required to resolve the Class to which the given path is relative to.
// If the path is 'foo.bar' and the context is 'foo.bar.baz.key' the latter
// can be used to uniquely identify which Class is the context of the path.
AbsolutePath(path data.Path, context data.Path) (data.Path, error)
}

type DataContainer interface {
DataSetterGetter
DataWalker
AbsolutePathMaker
AllPaths() []data.Path
LeafPaths() []data.Path
}

// Class defines the main file-data abstraction used by skipper.
// Every file with hierarchical data can be represented by a Class.
type Class struct {
Expand All @@ -42,7 +75,7 @@ type Class struct {
FilePath string
// Access to the underlying container is usually not advised.
// The Class itself exposes all the functionality of the container anyway.
container *data.Container
container DataContainer
// The class allows hooks to be registered to monitor each call to Set
preSetHook SetHookFunc
postSetHook SetHookFunc
Expand Down Expand Up @@ -97,44 +130,57 @@ func NewClass(filePath string, codec Codec, identifier ClassIdentifier) (*Class,
// Get the value at the given Path.
// Wrapper for [data.Container#Get]
func (c Class) Get(path string) (data.Value, error) {
return c.container.Get(data.NewPath(path))
return c.container.GetPath(data.NewPath(path))
}

func (c Class) GetPath(path data.Path) (data.Value, error) {
return c.container.Get(path)
return c.container.GetPath(path)
}

func (c Class) HasPath(path data.Path) bool {
_, err := c.container.GetPath(path)
if err != nil {
return false
}
return true
}

// GetAll returns the whole data represented by this class.
// Wrapper for [data.Container#Get]
func (c Class) GetAll() data.Value {
ret, _ := c.container.Get(data.NewPath(""))
ret, _ := c.container.GetPath(data.NewPath(""))
return ret
}

// Set will set the given value at the specified path.
// Wrapper for [data.Container#Set]
func (c *Class) Set(path string, value interface{}) error {
absPath, err := c.container.AbsolutePath(data.NewPath(path), nil)
if err != nil {
return err
}

if c.preSetHook != nil {
err := c.preSetHook(*c, c.container.AbsolutePath(data.NewPath(path)), data.NewValue(value))
err = c.preSetHook(*c, absPath, data.NewValue(value))
if err != nil {
return err
}
}

err := c.container.Set(data.NewPath(path), value)
err = c.container.SetPath(absPath, value)
if err != nil {
return err
}

if c.postSetHook != nil {
return c.postSetHook(*c, c.container.AbsolutePath(data.NewPath(path)), data.NewValue(value))
return c.postSetHook(*c, absPath, data.NewValue(value))
}

return nil
}

func (c *Class) SetPath(path data.Path, value interface{}) error {
return c.Set(path.String(), value)
return c.container.SetPath(path, value)
}

// SetPreSetHook sets the preSetHook of the class
Expand Down Expand Up @@ -167,7 +213,7 @@ func (c *Class) AllPaths() []data.Path {
}

// Walk allows traversing the underlying container data.
func (c *Class) Walk(walkFunc data.WalkContainerFunc) error {
func (c *Class) Walk(walkFunc func(data.Path, data.Value, bool) error) error {
return c.container.Walk(walkFunc)
}

Expand All @@ -183,6 +229,15 @@ func (c *Class) WalkValues(walkFunc func(data.Path, data.Value) error) error {
})
}

// AbsolutePath ensures that the given path is absolute within the given context path.
// This function satisfies the [skipper.AbsolutePathMaker] interface.
// The second parameter is usually required to determine to which Class the path is relative to.
// In this case, that context is not needed as there is only this Class context.
// In case the path is empty or it is not valid within the given context, an error is returned.
func (c *Class) AbsolutePath(path data.Path, context data.Path) (data.Path, error) {
return c.container.AbsolutePath(path, context)
}

// ClassLoader is a simple helper function which accepts a list of paths which will be loaded a Classes.
func ClassLoader(basePath string, files []string, codec Codec) ([]*Class, error) {
var classes []*Class
Expand Down
59 changes: 39 additions & 20 deletions data/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@ import (
)

var (
ErrEmptyRootKeyName = fmt.Errorf("empty root key name")
ErrNilData = fmt.Errorf("data is nil")
ErrNoRootKey = fmt.Errorf("data has no root key (empty)")
ErrMultipleRootKeys = fmt.Errorf("multiple root keys")
ErrInvalidRootKey = fmt.Errorf("invalid root key")
ErrNestedArrayPath = fmt.Errorf("nested array paths are currently not supported")
ErrEmptyRootKeyName = fmt.Errorf("empty root key name")
ErrNilData = fmt.Errorf("data is nil")
ErrNoRootKey = fmt.Errorf("data has no root key (empty)")
ErrMultipleRootKeys = fmt.Errorf("multiple root keys")
ErrInvalidRootKey = fmt.Errorf("invalid root key")
ErrNestedArrayPath = fmt.Errorf("nested array paths are currently not supported")
ErrCannotResolveAbsolutePath = fmt.Errorf("cannot resolve absolute path")
)

// Container holds the raw data and provides access to it.
Expand Down Expand Up @@ -82,7 +83,7 @@ func NewContainer(rootKeyName string, data map[string]interface{}) (*Container,
// use the path 'foo.bar' or 'bar' to address the same value.
//
// If the path does not exist, a [ErrPathNotFound] error is returned.
func (container *Container) Get(path Path) (Value, error) {
func (container *Container) GetPath(path Path) (Value, error) {
if len(path) == 0 {
return NewValue(container.data), nil
}
Expand Down Expand Up @@ -115,12 +116,15 @@ func (container *Container) Get(path Path) (Value, error) {
// found, an error is returned for the same reason; types are not changed.
// This means that if there is a scalar value at 'foo.bar.baz', you
// cannot set 'foo.bar.baz.qux'.
func (container *Container) Set(path Path, value interface{}) error {
func (container *Container) SetPath(path Path, value interface{}) error {
if len(path) == 0 {
return ErrEmptyPath
}

path = container.AbsolutePath(path)
path, err := container.AbsolutePath(path, nil)
if err != nil {
return err
}

ret, err := DeepSet(container.data, path, value)
if err != nil {
Expand All @@ -147,7 +151,7 @@ func (container *Container) Set(path Path, value interface{}) error {
// Note: Only map types can be merged! So the given path must point to a map type.
// TODO: Should empty paths (aka full data merging) be allowed?
func (container *Container) Merge(path Path, data map[string]interface{}) error {
inputData, err := container.Get(path)
inputData, err := container.GetPath(path)
if err != nil {
return err
}
Expand All @@ -159,21 +163,23 @@ func (container *Container) Merge(path Path, data map[string]interface{}) error

replaced := Merge(inputDataMap, data)

err = container.Set(path, NewValue(replaced))
err = container.SetPath(path, NewValue(replaced))
if err != nil {
return err
}

return nil
}

type WalkContainerFunc func(path Path, value Value, isLeaf bool) error

// Walk is the container implementation of the general [Walk] function.
// The only difference is that it uses [Value] types instead of arbitrary interfaces.
func (container *Container) Walk(walkContainerFunc WalkContainerFunc) error {
func (container *Container) Walk(walkFunc func(path Path, value Value, isLeaf bool) error) error {
return Walk(container.data, func(path Path, data interface{}, isLeaf bool) error {
return walkContainerFunc(container.AbsolutePath(path), NewValue(data), isLeaf)
absPath, err := container.AbsolutePath(path, nil)
if err != nil {
return err
}
return walkFunc(absPath, NewValue(data), isLeaf)
})
}

Expand All @@ -189,7 +195,7 @@ func (container *Container) LeafPaths() []Path {

// MustGet is a wrapper around [Container.Get] which panics on error.
func (container *Container) MustGet(path Path) Value {
val, err := container.Get(path)
val, err := container.GetPath(path)
if err != nil {
panic(err)
}
Expand All @@ -198,16 +204,29 @@ func (container *Container) MustGet(path Path) Value {

// HasPath returns true if the given path exists, false otherwise.
func (container *Container) HasPath(path Path) bool {
if _, err := container.Get(path); err != nil {
if _, err := container.GetPath(path); err != nil {
return false
}
return true
}

// AbsolutePath ensures that the given path is absolute.
func (container *Container) AbsolutePath(path Path) Path {
// AbsolutePath ensures that the given path is absolute within the given context path.
// This function satisfies the [skipper.AbsolutePathMaker] interface.
// The second parameter is usually required to determine to which Class the path is relative to.
// In this case, that context is not needed as there is only the one container context.
// In case the path is empty or it is not valid within the given context, an error is returned.
func (container *Container) AbsolutePath(path Path, _ Path) (Path, error) {
if path == nil || len(path) == 0 {
return nil, ErrEmptyPath
}

if path.First() != container.rootKey {
path = path.Prepend(container.rootKey)
}
return path

if !container.HasPath(path) {
return nil, fmt.Errorf("%w: path does not exist: %s", ErrCannotResolveAbsolutePath, path)
}

return path, nil
}
Loading

0 comments on commit 927977c

Please sign in to comment.