Skip to content

Commit

Permalink
Attempt of using generics instead of interface{} in component's
Browse files Browse the repository at this point in the history
configuration handling and creation.

Drafted solution lives for now in `generic` package and example usage
(with some fake test component) is demonstrated in `component_test.go`.
  • Loading branch information
pondzix committed Jul 2, 2024
1 parent d486101 commit 0a893cc
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 0 deletions.
45 changes: 45 additions & 0 deletions config/generic/component.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package generic

import (
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/gohcl"
)

// Implemented by each component, like kinesis target

Check failure on line 8 in config/generic/component.go

View workflow job for this annotation

GitHub Actions / Compile & Test (1.22, ubuntu-latest)

comment on exported type ConfigurableComponent should be of the form "ConfigurableComponent ..." (with optional leading article)
type ConfigurableComponent[CONFIG any, COMPONENT any] interface {
DefaultConfig() *CONFIG
FromConfig(config *CONFIG) (*COMPONENT, error)
}

// Also implemented by each component, but generic type parameter 'OUT' specifies interface requested by consumer, e.g. 'Target' interface.

Check failure on line 14 in config/generic/component.go

View workflow job for this annotation

GitHub Actions / Compile & Test (1.22, ubuntu-latest)

comment on exported type ProviderOf should be of the form "ProviderOf ..." (with optional leading article)
type ProviderOf[OUT any] interface {
Provide(input hcl.Body, ctx *hcl.EvalContext) (OUT, error)
}

func createComponent[CONFIG any, COMPONENT any](component ConfigurableComponent[CONFIG, COMPONENT], input hcl.Body, ctx *hcl.EvalContext) (*COMPONENT, error) {
config := component.DefaultConfig()
decode[CONFIG](input, ctx, config)
readyComponent, err := component.FromConfig(config)

if err != nil {
return nil, err
}
return readyComponent, nil
}

func decode[CONFIG any](input hcl.Body, evalContext *hcl.EvalContext, configuration *CONFIG) error {
if input == nil {
return nil
}

if configuration == nil {
return nil
}

diag := gohcl.DecodeBody(input, evalContext, configuration)
if len(diag) > 0 {
return diag
}

return nil
}
26 changes: 26 additions & 0 deletions config/generic/component_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package generic

import (
"testing"

"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclparse"
"github.com/stretchr/testify/assert"
)

var supportedTargets = map[string]ProviderOf[Target]{
"testTarget": &TestProvider{},
}

func Test_CreatingTarget(t *testing.T) {
assert := assert.New(t)
hclFile, _ := hclparse.NewParser().ParseHCL([]byte(`test_string = "ateststring"`), "placeholder.hcl")
input := hclFile.Body

target, _ := supportedTargets["testTarget"].Provide(input, &hcl.EvalContext{})

output := target.Write("Test string")

assert.Equal("Written: Test string. This is value in config - ateststring", output)

}
34 changes: 34 additions & 0 deletions config/generic/example_component.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package generic

import (
"github.com/hashicorp/hcl/v2"
)

type myComponentConfig struct {
Test string `hcl:"test_string"`
}

type myComponent struct {
config myComponentConfig
}

// This one is used by component's clients. If you want ot use component, just use this struct

Check failure on line 15 in config/generic/example_component.go

View workflow job for this annotation

GitHub Actions / Compile & Test (1.22, ubuntu-latest)

comment on exported type TestProvider should be of the form "TestProvider ..." (with optional leading article)
type TestProvider struct{}

// Return component as interface, here as 'Target'

Check failure on line 18 in config/generic/example_component.go

View workflow job for this annotation

GitHub Actions / Compile & Test (1.22, ubuntu-latest)

comment on exported method TestProvider.Provide should be of the form "Provide ..."
func (cc TestProvider) Provide(input hcl.Body, ctx *hcl.EvalContext) (Target, error) {
return createComponent[myComponentConfig, myComponent](cc, input, ctx)
}

func (TestProvider) DefaultConfig() *myComponentConfig {

Check failure on line 23 in config/generic/example_component.go

View workflow job for this annotation

GitHub Actions / Compile & Test (1.22, ubuntu-latest)

exported method TestProvider.DefaultConfig should have comment or be unexported

Check failure on line 23 in config/generic/example_component.go

View workflow job for this annotation

GitHub Actions / Compile & Test (1.22, ubuntu-latest)

exported method DefaultConfig returns unexported type *generic.myComponentConfig, which can be annoying to use
return &myComponentConfig{"some default value"}
}

func (TestProvider) FromConfig(config *myComponentConfig) (*myComponent, error) {

Check failure on line 27 in config/generic/example_component.go

View workflow job for this annotation

GitHub Actions / Compile & Test (1.22, ubuntu-latest)

exported method TestProvider.FromConfig should have comment or be unexported

Check failure on line 27 in config/generic/example_component.go

View workflow job for this annotation

GitHub Actions / Compile & Test (1.22, ubuntu-latest)

exported method FromConfig returns unexported type *generic.myComponent, which can be annoying to use
return &myComponent{*config}, nil
}

// Implementing desired interfaces, like 'Target'
func (mc myComponent) Write(input string) string {
return "Written: " + input + ". This is value in config - " + mc.config.Test
}
5 changes: 5 additions & 0 deletions config/generic/target.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package generic

type Target interface {

Check failure on line 3 in config/generic/target.go

View workflow job for this annotation

GitHub Actions / Compile & Test (1.22, ubuntu-latest)

exported type Target should have comment or be unexported
Write(input string) string
}

0 comments on commit 0a893cc

Please sign in to comment.